diff --git a/.claude/skills/bootstrap/SKILL.md b/.claude/skills/bootstrap/SKILL.md new file mode 100644 index 0000000000000..37c927dd75fe1 --- /dev/null +++ b/.claude/skills/bootstrap/SKILL.md @@ -0,0 +1,31 @@ +--- +name: bootstrap +description: Setup both Firefox and Focus for iOS after fetching from git. +allowed-tools: Bash(brew *) Bash(which *) +--- + +First, check if `fxios` is installed: + +``` +which fxios +``` + +If it is not installed, run: + +``` +brew tap mozilla-mobile/fxios +brew install fxios +``` + +If it is installed, upgrade to the latest version: + +``` +brew upgrade fxios +``` + +Then run these steps in sequence from the root of the firefox-ios repository: + +1. `fxios --version` +2. `fxios bootstrap --all` + +Stop and report if any step fails. \ No newline at end of file diff --git a/.github/workflows/check-kingfisher-dependency.yml b/.github/workflows/check-kingfisher-dependency.yml new file mode 100644 index 0000000000000..e116130ea639c --- /dev/null +++ b/.github/workflows/check-kingfisher-dependency.yml @@ -0,0 +1,101 @@ +name: Create a PR to update Kingfisher to the newest version available +on: + schedule: + - cron: '0 8 * * 1' + workflow_dispatch: + inputs: + branchName: + description: 'Branch used as target for automation' + required: true + default: 'main' +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 4 + matrix: + python-version: [3.10.16] + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r ./test-fixtures/requirements.txt + + - name: Update Kingfisher version in Package.swift and all Package.resolved files + run: | + python ./test-fixtures/update-kingfisher-version.py + + - name: Get new Kingfisher version to be used in the PR info + run: | + if [ -f test-fixtures/newest_kingfisher_tag.txt ]; then + echo "kingfisher_version=$(cat test-fixtures/newest_kingfisher_tag.txt | tr -d '[:space:]')" >> $GITHUB_ENV + fi + + - name: Remove temp file created to store the tag info + run: | + [ ! -e test-fixtures/newest_kingfisher_tag.txt ] || rm test-fixtures/newest_kingfisher_tag.txt + + - name: Script to check if branch exists to not commit again + run: |- + branch=$(curl -X GET -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/mozilla-mobile/firefox-ios/branches?per_page=100 | jq -r '.[].name | select(contains("update-spm-new-kingfisher-tag-${{ env.kingfisher_version }}"))') + echo $branch + if [ -z "$branch" ]; then echo "BRANCH_CREATED=false" >> $GITHUB_ENV; else echo "BRANCH_CREATED=true" >> $GITHUB_ENV; fi + + - name: Determine PR Version Number + id: versioning + run: | + output=$(bash test-fixtures/ci/get-next-pr-version) + echo "$output" + next_version=$(echo "$output" | tail -n 1) + echo "Next Firefox iOS version is: v${next_version}" + echo "next_app_version=${next_version}" >> $GITHUB_ENV + + - name: Stage updated files + if: env.BRANCH_CREATED == 'false' && env.kingfisher_version != '' + run: |- + git diff + git diff --quiet || (git add \ + BrowserKit/Package.swift \ + BrowserKit/Package.resolved \ + firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ + SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved \ + focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + if: env.BRANCH_CREATED == 'false' && env.kingfisher_version != '' + with: + commit-message: Auto update SPM with latest Kingfisher release ${{ env.kingfisher_version }} + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + committer: GitHub + title: Bump FXIOS [Dependency] Auto update SPM for v${{ env.next_app_version }} with latest Kingfisher ${{ env.kingfisher_version }} + branch: update-spm-new-kingfisher-tag-${{ env.kingfisher_version }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set job log URL + if: always() + run: echo "JOB_LOG_URL=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_ENV + + - name: Send Slack notification if action fails + if: '!cancelled()' + id: slack + uses: slackapi/slack-github-action@v2.0.0 + env: + ACTION_NAME: Kingfisher dependency update + JOB_STATUS: ${{ job.status == 'success' && ':white_check_mark:' || job.status == 'failure' && ':x:' }} + JOB_STATUS_COLOR: ${{ job.status == 'success' && '#36a64f' || job.status == 'failure' && '#FF0000' }} + with: + payload-file-path: "./test-fixtures/ci/slack-notification-payload-generic.json" + payload-templated: true + webhook: ${{ secrets.WEBHOOK_SLACK_TOKEN }} + webhook-type: incoming-webhook diff --git a/.github/workflows/firefox-ios-autofill-playwrite-tests.yml b/.github/workflows/firefox-ios-autofill-playwrite-tests.yml index 53f7da9deb60b..8e8b13dd1afce 100644 --- a/.github/workflows/firefox-ios-autofill-playwrite-tests.yml +++ b/.github/workflows/firefox-ios-autofill-playwrite-tests.yml @@ -56,10 +56,11 @@ jobs: id: slack uses: slackapi/slack-github-action@v2.0.0 with: - payload-file-path: "./test-fixtures/ci/slack-notification-payload-autofill-test.json" + payload-file-path: "./test-fixtures/ci/slack-notification-payload-generic.json" payload-templated: true webhook: ${{ secrets.WEBHOOK_SLACK_TOKEN }} webhook-type: incoming-webhook env: + ACTION_NAME: credential provider playwrite tests JOB_STATUS: ${{ job.status == 'success' && ':white_check_mark:' || job.status == 'failure' && ':x:' }} JOB_STATUS_COLOR: ${{ job.status == 'success' && '#36a64f' || job.status == 'failure' && '#FF0000' }} diff --git a/.github/workflows/firefox-ios-update_credential_provider_script.yml b/.github/workflows/firefox-ios-update_credential_provider_script.yml index 108207e98d05e..5d62ab85c8f92 100644 --- a/.github/workflows/firefox-ios-update_credential_provider_script.yml +++ b/.github/workflows/firefox-ios-update_credential_provider_script.yml @@ -55,10 +55,11 @@ jobs: id: slack uses: slackapi/slack-github-action@v2.0.0 env: + ACTION_NAME: credential provider script JOB_STATUS: ${{ job.status == 'success' && ':white_check_mark:' || job.status == 'failure' && ':x:' }} JOB_STATUS_COLOR: ${{ job.status == 'success' && '#36a64f' || job.status == 'failure' && '#FF0000' }} with: - payload-file-path: "./test-fixtures/ci/slack-notification-payload-autofill.json" + payload-file-path: "./test-fixtures/ci/slack-notification-payload-generic.json" payload-templated: true webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook diff --git a/.github/workflows/firefox-ios-update_remote_settings_data_script.yml b/.github/workflows/firefox-ios-update_remote_settings_data_script.yml index 1eba242a3bc00..f9d0cf7dd5491 100644 --- a/.github/workflows/firefox-ios-update_remote_settings_data_script.yml +++ b/.github/workflows/firefox-ios-update_remote_settings_data_script.yml @@ -67,10 +67,11 @@ jobs: id: slack uses: slackapi/slack-github-action@v2.0.0 env: + ACTION_NAME: remote settings fetch JOB_STATUS: ${{ job.status == 'success' && ':white_check_mark:' || job.status == 'failure' && ':x:' }} JOB_STATUS_COLOR: ${{ job.status == 'success' && '#36a64f' || job.status == 'failure' && '#FF0000' }} with: - payload-file-path: "./test-fixtures/ci/slack-notification-payload-remote-settings-fetch.json" + payload-file-path: "./test-fixtures/ci/slack-notification-payload-generic.json" payload-templated: true webhook: ${{ secrets.WEBHOOK_SLACK_TOKEN }} webhook-type: incoming-webhook \ No newline at end of file diff --git a/BrowserKit/.swiftpm/xcode/xcshareddata/xcschemes/QuickAnswersKit.xcscheme b/BrowserKit/.swiftpm/xcode/xcshareddata/xcschemes/QuickAnswersKit.xcscheme index f1431c570d01c..d3269033f0730 100644 --- a/BrowserKit/.swiftpm/xcode/xcshareddata/xcschemes/QuickAnswersKit.xcscheme +++ b/BrowserKit/.swiftpm/xcode/xcshareddata/xcschemes/QuickAnswersKit.xcscheme @@ -28,7 +28,30 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" + onlyGenerateCoverageForSpecifiedTargets = "YES" shouldAutocreateTestPlan = "YES"> + + + + + + + + + + Void)? + private var onScroll: ((CGFloat) -> Void)? override public init(frame: CGRect) { super.init(frame: frame) + clipsToBounds = false setupLayout() } @@ -39,12 +42,16 @@ public final class ChipPickerView: UIView, ThemeApplicable { public func configure( items: [ChipPickerItem], selectedID: String?, + contentOffsetX: CGFloat = 0, + onScroll: ((CGFloat) -> Void)? = nil, onSelection: (@MainActor (String) -> Void)? = nil ) { self.items = items self.selectedID = selectedID self.onSelection = onSelection + self.onScroll = onScroll rebuildButtons() + updateContentOffsetX(contentOffsetX) } public func applyTheme(theme: Theme) { @@ -52,6 +59,16 @@ public final class ChipPickerView: UIView, ThemeApplicable { chipButtons.forEach { $0.applyTheme(theme: theme) } } + public func updateSelectedID(_ selectedID: String?) { + self.selectedID = selectedID + rebuildButtons() + scrollView.layoutIfNeeded() + } + + public func updateContentOffsetX(_ contentOffsetX: CGFloat) { + scrollView.setContentOffset(CGPoint(x: contentOffsetX, y: 0), animated: false) + } + private var chipButtons: [ChipButton] { stackView.arrangedSubviews.compactMap { $0 as? ChipButton } } @@ -59,6 +76,7 @@ public final class ChipPickerView: UIView, ThemeApplicable { private func setupLayout() { addSubview(scrollView) scrollView.addSubview(stackView) + scrollView.delegate = self NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: topAnchor), @@ -104,4 +122,10 @@ public final class ChipPickerView: UIView, ThemeApplicable { rebuildButtons() onSelection?(id) } + + // MARK: = UIScrollViewDelegate + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + onScroll?(scrollView.contentOffset.x) + } } diff --git a/BrowserKit/Sources/LLMKit/LiteLLMCreator.swift b/BrowserKit/Sources/LLMKit/LiteLLMCreator.swift index d6d655a2e5e60..f4fcdb0a52c69 100644 --- a/BrowserKit/Sources/LLMKit/LiteLLMCreator.swift +++ b/BrowserKit/Sources/LLMKit/LiteLLMCreator.swift @@ -4,8 +4,14 @@ import Shared import MLPAKit -public struct LiteLLMCreator { - public static func createAppAttestLiteLLM(using prefs: Prefs) -> LiteLLMClient? { +public protocol LiteLLMCreating { + func createAppAttestLiteLLM(using prefs: Prefs) -> LiteLLMClientProtocol? +} + +public struct LiteLLMCreator: LiteLLMCreating { + public init() { } + + public func createAppAttestLiteLLM(using prefs: Prefs) -> LiteLLMClientProtocol? { let mlpaEnvironmentKey = prefs.stringForKey(PrefsKeys.MLPASettings.mlpaEndpointEnvironment) ?? "" let mlpaEnvironment = MLPAEnvironment(rawValue: mlpaEnvironmentKey) ?? .prod guard let endPoint = MLPAConstants.completionsEndpoint(with: mlpaEnvironment), diff --git a/BrowserKit/Sources/MLPAKit/MLPAJWTPayload.swift b/BrowserKit/Sources/MLPAKit/MLPAJWTPayload.swift index 9306bbabf322e..0599bae912e71 100644 --- a/BrowserKit/Sources/MLPAKit/MLPAJWTPayload.swift +++ b/BrowserKit/Sources/MLPAKit/MLPAJWTPayload.swift @@ -19,6 +19,9 @@ public enum MLPAConstants { static let assertionObjParam = "assertion_obj_b64" static let serviceTypeHeader = "service-type" + + // TODO: FXIOS-15123 - need to create an enum service type here + // eventually to also account for quick-answers static let serviceTypeValue = "s2s" static let useAppAttestHeader = "use-app-attest" diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/DefaultQuickAnswersService.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/DefaultQuickAnswersService.swift index 48d5efe6d0acc..8996a2819093a 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/DefaultQuickAnswersService.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/DefaultQuickAnswersService.swift @@ -3,6 +3,8 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Foundation +import LLMKit +import Shared /// A concrete `QuickAnswersService` that records speech and emits transcription results /// using an underlying `TranscriptionEngine`. As well as request results from the transcription via `ResultsService` flow. @@ -26,10 +28,18 @@ final class DefaultQuickAnswersService: QuickAnswersService { /// Creates a new service with a platform-appropriate transcription engine and results service. init( engine: TranscriptionEngine? = nil, - resultsService: ResultsService? = nil - ) { + resultsServiceFactory: ResultsServiceFactory = DefaultResultsServiceFactory( + config: QuickAnswersConfig(), + liteLLMCreator: LiteLLMCreator() + ), + prefs: Prefs + ) throws { self.engine = engine ?? Self.makeDefaultEngine() - self.resultsService = resultsService ?? Self.makeResultsService() + let config = QuickAnswersConfig() + guard let resultsService = resultsServiceFactory.make(prefs: prefs, config: config) else { + throw ResultsServiceError.unableToCreateService + } + self.resultsService = resultsService } // MARK: Speech Service @@ -114,11 +124,4 @@ final class DefaultQuickAnswersService: QuickAnswersService { ) } } - - private static func makeResultsService() -> ResultsService { - let factory = DefaultResultsServiceFactory( - config: QuickAnswersConfig() - ) - return factory.make() - } } diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/MockQuickAnswersService.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/MockQuickAnswersService.swift index 8a2456bc27b68..f85cf7a2339d6 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/MockQuickAnswersService.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/MockQuickAnswersService.swift @@ -43,11 +43,8 @@ struct MockQuickAnswersService: QuickAnswersService { } return .success( SearchResult( - title: "The weather in Paris is cloudy", - body: "The weather is cloudy with a chance of rain at 18:00", - url: URL( - string: "https://weather.com/weather/today" - ) + resultText: "The weather is cloudy with a chance of rain at 18:00", + sources: [SearchResult.Source(title: "Weather Channel", thumbnailURL: nil, faviconURL: nil)] ) ) } diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/QuickAnswersService.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/QuickAnswersService.swift index 578678d7a96df..0cf82fb25df5a 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/QuickAnswersService.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/QuickAnswersService.swift @@ -14,12 +14,17 @@ struct SpeechResult: Equatable { } struct SearchResult: Equatable { - let title: String - let body: String - let url: URL? + struct Source: Equatable { + let title: String + let thumbnailURL: URL? + let faviconURL: URL? + } + + let resultText: String + let sources: [SearchResult.Source] static func empty() -> Self { - return SearchResult(title: "", body: "", url: nil) + return SearchResult(resultText: "", sources: []) } } diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/DefaultResultsService.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/DefaultResultsService.swift index ad53097d424c1..92f2f76d13840 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/DefaultResultsService.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/DefaultResultsService.swift @@ -12,33 +12,26 @@ protocol ResultsService: Sendable { } final class DefaultResultsService: ResultsService { - // TODO: FXIOS-15196 - Remove optional when creating the appropriate client - private let client: LiteLLMClientProtocol? - private let config: QuickAnswersConfig + private let client: LiteLLMClientProtocol + private let config: LLMConfig - init(client: LiteLLMClientProtocol?, config: QuickAnswersConfig) { + init(client: LiteLLMClientProtocol, config: LLMConfig) { self.client = client self.config = config } func fetchResults(for transcription: String) async throws -> SearchResult { let message = LiteLLMMessage(role: .user, content: transcription) - guard let stream = try await requestChatCompletion(for: message) else { - // TODO: FXIOS-15196 - Remove error once LLMCLient is not nil - throw SpeechError.unknown - } - - var fullResponse = "" - for try await chunk in stream { - fullResponse += chunk - } - + // TODO: FXIOS-15198 - Handle mapping errors from request + let fullResponse = try await requestChatCompletion(for: message) return try formatResult(from: fullResponse) } - private func requestChatCompletion(for message: LiteLLMMessage) async throws -> AsyncThrowingStream? { - // TODO: FXIOS-15196 - Remove optional when creating the appropriate client - return try await client?.requestChatCompletionStreamed( + private func requestChatCompletion(for message: LiteLLMMessage) async throws -> String { + // TODO: FXIOS-15198 Handle errors appropriately + // and may need to change type and not use String, + // but waiting for what we get on server side + return try await client.requestChatCompletion( messages: [message], config: config ) @@ -46,7 +39,13 @@ final class DefaultResultsService: ResultsService { private func formatResult(from response: String) throws -> SearchResult { // TODO: FXIOS-15197 - Implement parsing logic based on response format and update Search Result - // depending on UI to be a stream instead. For now, return the full response as the body. - return SearchResult(title: "Quick Answer", body: response, url: nil) + return SearchResult( + resultText: response, + sources: [SearchResult.Source( + title: "", + thumbnailURL: nil, + faviconURL: nil + )] + ) } } diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/QuickAnswersConfig.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/QuickAnswersConfig.swift index 234833a47de37..d6e2427bf5a9e 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/QuickAnswersConfig.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/QuickAnswersConfig.swift @@ -4,12 +4,21 @@ import LLMKit -// TODO: FXIOS-15196 We may not need a configuration if we only want to pass in the transcription, -// may need to refactor `LiteLLMClient` /// A configuration container for the quick answers results feature. public struct QuickAnswersConfig: LLMConfig, Sendable { - public let instructions = "" + public let instructions: String // FIXME: FXIOS-13417 We should strongly type options in the future so they can be any Sendable & Hashable // See `SummarizerConfig` for more details - nonisolated(unsafe) public let options: [String: AnyHashable] = [:] + nonisolated(unsafe) public let options: [String: AnyHashable] + + // TODO: FXIOS-15123 - need confirm we want to pass these options, follow similar to S2S for now + /// Default initializer with production configuration + public init(instructions: String = "", options: [String: AnyHashable] = [ + "max_tokens": LiteLLMConfig.maxTokens, + "model": LiteLLMConfig.apiModel, + "stream": true + ]) { + self.instructions = instructions + self.options = options + } } diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceError.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceError.swift new file mode 100644 index 0000000000000..1244cf1caa987 --- /dev/null +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceError.swift @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +enum ResultsServiceError: Error { + case unableToCreateService + case unableToFetchResults +} diff --git a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceFactory.swift b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceFactory.swift index df07e803b5d60..38ba6a46629a6 100644 --- a/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceFactory.swift +++ b/BrowserKit/Sources/QuickAnswersKit/Backend/ResultsService/ResultsServiceFactory.swift @@ -5,27 +5,42 @@ import Foundation import MLPAKit import LLMKit +import Shared // MARK: - Protocol /// Creates a ResultsService with using MLPA (App Attest) authentication and LiteLLM. protocol ResultsServiceFactory { - func make() -> ResultsService + func make(prefs: Prefs, config: QuickAnswersConfig) -> ResultsService? } // MARK: - Default Implementation public struct DefaultResultsServiceFactory: ResultsServiceFactory { let config: QuickAnswersConfig + let liteLLMCreator: LiteLLMCreating + + public init( + config: QuickAnswersConfig, + liteLLMCreator: LiteLLMCreating + ) { + self.config = config + self.liteLLMCreator = liteLLMCreator + } + + func make( + prefs: Prefs, + config: QuickAnswersConfig = QuickAnswersConfig() + ) -> ResultsService? { + guard let model = config.options["model"] as? String, !model.isEmpty, + let client = makeLiteLLMClient(prefs: prefs) + else { + return nil + } - func make() -> ResultsService { - // TODO: FXIOS-15196 - Create Results Service from the LiteLLMClient, remove optional when implementing appropriate - // LiteLLMClient - let client = makeLiteLLMClient() return DefaultResultsService(client: client, config: config) } // MARK: - Private Helpers - private func makeLiteLLMClient() -> LiteLLMClient? { - // TODO: FXIOS-15196 - Create LiteLLMClient with proper configurations and authenticator - return nil + private func makeLiteLLMClient(prefs: Prefs) -> LiteLLMClientProtocol? { + return liteLLMCreator.createAppAttestLiteLLM(using: prefs) } } diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/Contents.json b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/Contents.json new file mode 100644 index 0000000000000..73c00596a7fca --- /dev/null +++ b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/Contents.json b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/Contents.json new file mode 100644 index 0000000000000..ba8373ca33c00 --- /dev/null +++ b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "shield.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/shield.pdf b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/shield.pdf new file mode 100644 index 0000000000000..debb079f6b9c0 Binary files /dev/null and b/BrowserKit/Sources/QuickAnswersKit/UI/Media.xcassets/shield.imageset/shield.pdf differ diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersContentView.swift b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersContentView.swift index b60f3f4ee5c01..f06a612361810 100644 --- a/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersContentView.swift +++ b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersContentView.swift @@ -16,6 +16,7 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { private let scrollView: UIScrollView = .build { $0.showsVerticalScrollIndicator = false $0.alwaysBounceVertical = false + $0.clipsToBounds = false } private let contentView: UIView = .build() private let placeholderLabel: UILabel = .build { @@ -23,20 +24,27 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { $0.text = "Ask anything…" $0.numberOfLines = 0 $0.textAlignment = .center + $0.adjustsFontForContentSizeCategory = true } private let transcriptLabel: UILabel = .build { $0.font = FXFontStyles.Regular.title2.scaledFont() $0.numberOfLines = 0 + $0.adjustsFontForContentSizeCategory = true } private let searchingLabel: UILabel = .build { $0.font = FXFontStyles.Bold.callout.scaledFont() $0.text = "Answering…" $0.alpha = 0.0 + $0.adjustsFontForContentSizeCategory = true } private let answerLabel: UILabel = .build { $0.font = FXFontStyles.Regular.body.scaledFont() $0.numberOfLines = 0 $0.alpha = 0.0 + $0.adjustsFontForContentSizeCategory = true + } + private let sourceView: QuickAnswersSourceView = .build { + $0.alpha = 0.0 } private var theme: Theme? @@ -52,7 +60,7 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { // MARK: - Setup private func setupSubviews() { - contentView.addSubviews(placeholderLabel, transcriptLabel, searchingLabel, answerLabel) + contentView.addSubviews(placeholderLabel, transcriptLabel, searchingLabel, answerLabel, sourceView) scrollView.addSubview(contentView) addSubview(scrollView) @@ -79,11 +87,19 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { answerLabel.topAnchor.constraint(equalTo: transcriptLabel.bottomAnchor, constant: UX.contentSpacing), answerLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), answerLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - answerLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + + sourceView.topAnchor.constraint(equalTo: answerLabel.bottomAnchor, constant: UX.contentSpacing), + sourceView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + sourceView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + sourceView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } // MARK: - Configuration + func adjustBottomInsets(for height: CGFloat) { + scrollView.contentInset.bottom = height + } + func configureTranscript(_ text: String) { // if the placeholder is visible then hide it before adding text to the transcription label. // This is needed to don't overlap the show of the transcription with the placeholder label @@ -97,10 +113,9 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { } return } + transcriptLabel.text = text UIView.animate(withDuration: UX.animationDuration) { [self] in placeholderLabel.alpha = 0.0 - } completion: { [weak self] _ in - self?.transcriptLabel.text = text } } @@ -125,6 +140,13 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { } } + func configureSources(_ items: [SearchResult.Source]) { + sourceView.configure(with: items) + UIView.animate(withDuration: UX.animationDuration) { [self] in + sourceView.alpha = 1.0 + } + } + // MARK: - ThemeApplicable func applyTheme(theme: any Theme) { self.theme = theme @@ -132,5 +154,6 @@ final class QuickAnswersContentView: UIView, ThemeApplicable { transcriptLabel.textColor = theme.colors.textPrimary searchingLabel.textColor = theme.colors.textSecondary answerLabel.textColor = theme.colors.textPrimary + sourceView.applyTheme(theme: theme) } } diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersSourceView.swift b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersSourceView.swift new file mode 100644 index 0000000000000..6612be102500c --- /dev/null +++ b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersSourceView.swift @@ -0,0 +1,238 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 UIKit +import Common +import SiteImageView + +final class QuickAnswersSourceCell: UICollectionViewCell, ReusableCell, ThemeApplicable { + private struct UX { + static let thumbnailCornerRadius: CGFloat = 16.0 + static let thumbnailBorderWidth: CGFloat = 1.0 + static let thumbnailShadowBlurRadius: CGFloat = 64.0 + static let thumbnailShadowOffset = CGSize(width: 0.0, height: 8.0) + static let thumbnailShadowOpacity: Float = 1.0 + static let titleRowTopSpacing: CGFloat = 8.0 + static let titleRowSpacing: CGFloat = 4.0 + static let faviconSize: CGFloat = 16.0 + static let faviconCornerRadius: CGFloat = faviconSize / 2.0 + } + + private let thumbnailImageView: HeroImageView = .build() + private let faviconImageView: FaviconImageView = .build { + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + } + private let titleLabel: UILabel = .build { + $0.font = FXFontStyles.Regular.footnote.scaledFont() + $0.lineBreakMode = .byTruncatingTail + $0.adjustsFontForContentSizeCategory = true + $0.setContentCompressionResistancePriority(.required, for: .vertical) + } + private let titleStackView: UIStackView = .build { + $0.axis = .horizontal + $0.alignment = .center + $0.spacing = UX.titleRowSpacing + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + + private func setupSubviews() { + titleStackView.addArrangedSubview(faviconImageView) + titleStackView.addArrangedSubview(titleLabel) + contentView.addSubviews(thumbnailImageView, titleStackView) + + NSLayoutConstraint.activate([ + thumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor), + thumbnailImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + thumbnailImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + + titleStackView.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, + constant: UX.titleRowTopSpacing), + titleStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + titleStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + titleStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + + faviconImageView.widthAnchor.constraint(equalToConstant: UX.faviconSize), + faviconImageView.heightAnchor.constraint(equalToConstant: UX.faviconSize), + ]) + } + + // MARK: - Configuration + func configure(with item: SearchResult.Source) { + if let thumbnailURLString = item.thumbnailURL?.absoluteString { + let heroImageViewModel = DefaultHeroImageViewModel( + urlStringRequest: thumbnailURLString, + generalCornerRadius: UX.thumbnailCornerRadius, + faviconCornerRadius: UX.faviconCornerRadius, + faviconBorderWidth: UX.thumbnailBorderWidth, + heroImageSize: .zero, + fallbackFaviconSize: CGSize(width: UX.faviconSize, height: UX.faviconSize) + ) + thumbnailImageView.setHeroImage(heroImageViewModel) + } + faviconImageView.setFavicon( + FaviconImageViewModel( + siteURLString: item.faviconURL?.absoluteString, + faviconCornerRadius: UX.faviconCornerRadius + ) + ) + titleLabel.text = item.title + } + + // MARK: - ThemeApplicable + func applyTheme(theme: any Theme) { + thumbnailImageView.applyShadow( + FxShadow( + blurRadius: UX.thumbnailShadowBlurRadius, + offset: UX.thumbnailShadowOffset, + opacity: UX.thumbnailShadowOpacity, + colorProvider: { $0.colors.shadowDefault } + ), + theme: theme + ) + let heroImageColors = HeroImageViewColor( + faviconTintColor: theme.colors.iconPrimary, + faviconBackgroundColor: theme.colors.layer1, + faviconBorderColor: theme.colors.shadowStrong + ) + thumbnailImageView.updateHeroImageTheme(with: heroImageColors) + titleLabel.textColor = theme.colors.textSecondary + } +} + +// TODO: - FXIOS-14720 Add Strings and accessibility ids +final class QuickAnswersSourceView: UIView, + UICollectionViewDataSource, + UICollectionViewDelegateFlowLayout, + ThemeApplicable { + private struct UX { + static let headerSpacing: CGFloat = 8.0 + static let interItemSpacing: CGFloat = 16.0 + static let maxItemWidth: CGFloat = 150.0 + static let thumbnailAspectRatio: CGFloat = 3.0 / 4.0 + } + + private let headerLabel: UILabel = .build { + $0.font = FXFontStyles.Bold.caption1.scaledFont() + $0.text = "Sources" + $0.adjustsFontForContentSizeCategory = true + } + private lazy var collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.isScrollEnabled = false + collectionView.clipsToBounds = false + collectionView.backgroundColor = .clear + collectionView.register(cellType: QuickAnswersSourceCell.self) + collectionView.dataSource = self + collectionView.delegate = self + return collectionView + }() + + private var items: [SearchResult.Source] = [] + private var theme: Theme? + private var contentSizeObservation: NSKeyValueObservation? + + override init(frame: CGRect) { + super.init(frame: frame) + setupSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + private func setupSubviews() { + addSubviews(headerLabel, collectionView) + + NSLayoutConstraint.activate([ + headerLabel.topAnchor.constraint(equalTo: topAnchor), + headerLabel.leadingAnchor.constraint(equalTo: leadingAnchor), + headerLabel.trailingAnchor.constraint(equalTo: trailingAnchor), + + collectionView.topAnchor.constraint(equalTo: headerLabel.bottomAnchor, constant: UX.headerSpacing), + collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), + collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), + collectionView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + + contentSizeObservation = collectionView.observe( + \.contentSize, + options: [.new, .old] + ) { [weak self] _, change in + guard change.newValue != change.oldValue else { return } + DispatchQueue.main.async { + self?.invalidateIntrinsicContentSize() + } + } + } + + override var intrinsicContentSize: CGSize { + // We need to override the intrinsic content size since the SourceView is embedded into a scroll view + // this results in the collectionView not being able to calculate its intrinsic content size thus we need + // to calculate it directly. + let headerHeight = headerLabel.intrinsicContentSize.height + UX.headerSpacing + return CGSize( + width: UIView.noIntrinsicMetric, + height: headerHeight + collectionView.contentSize.height + ) + } + + // MARK: - Configuration + func configure(with items: [SearchResult.Source]) { + self.items = items + collectionView.reloadData() + } + + // MARK: - UICollectionViewDataSource + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return items.count + } + + func collectionView( + _ collectionView: UICollectionView, + cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(cellType: QuickAnswersSourceCell.self, for: indexPath) else { + return UICollectionViewCell() + } + cell.configure(with: items[indexPath.item]) + if let theme { + cell.applyTheme(theme: theme) + } + return cell + } + + // MARK: - UICollectionViewDelegateFlowLayout + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { + let availableWidth = collectionView.frame.width + let numberOfItemsPerRow = floor(availableWidth / UX.maxItemWidth) + let width = (availableWidth - numberOfItemsPerRow * UX.interItemSpacing) / numberOfItemsPerRow + return CGSize(width: width, height: width * UX.thumbnailAspectRatio) + } + + // MARK: - ThemeApplicable + func applyTheme(theme: any Theme) { + self.theme = theme + headerLabel.textColor = theme.colors.textPrimary + collectionView.reloadData() + } +} diff --git a/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersViewController.swift b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersViewController.swift index e5c11ad44356a..112876b9460a1 100644 --- a/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersViewController.swift +++ b/BrowserKit/Sources/QuickAnswersKit/UI/QuickAnswersViewController.swift @@ -21,6 +21,15 @@ public final class QuickAnswersViewController: UIViewController, Themeable { static let contentViewTopPadding: CGFloat = 32.0 static let contentViewBottomPadding: CGFloat = 12.0 static let contentViewHorizontalPadding: CGFloat = 24.0 + static let privacyButtonContentInset = NSDirectionalEdgeInsets( + top: 8.0, + leading: 8.0, + bottom: 8.0, + trailing: 12.0 + ) + static let privacyButtonImagePadding: CGFloat = 4.0 + static let privacyButtonCornerRadius: CGFloat = 16.0 + static let privacyButtonImageName = "shield" } // MARK: - Properties @@ -29,7 +38,7 @@ public final class QuickAnswersViewController: UIViewController, Themeable { } private let backgroundRecordEffect: GradientCircleView = .build() private let audioWaveform: AudioWaveformView = .build() - private let closeButton: UIButton = .build { + private lazy var closeButton: UIButton = .build { if #available(iOS 26, *) { $0.configuration = .prominentGlass() } else { @@ -38,6 +47,33 @@ public final class QuickAnswersViewController: UIViewController, Themeable { $0.configuration?.cornerStyle = .capsule $0.configuration?.image = UIImage(named: StandardImageIdentifiers.Large.cross)?.withRenderingMode(.alwaysTemplate) $0.configuration?.contentInsets = UX.closeButtonContentInset + $0.addAction( + UIAction(handler: { [weak self] _ in + self?.navigationHandler?.dismissQuickAnswers(with: nil) + }), + for: .touchUpInside + ) + } + private let privacyButton: UIButton = .build { + if #available(iOS 26, *) { + $0.configuration = .prominentGlass() + } else { + $0.configuration = .filled() + } + $0.configuration?.image = UIImage( + named: UX.privacyButtonImageName, + in: .module, + with: nil + ) + // TODO: - FXIOS-14720 Add Strings and accessibility ids + $0.configuration?.attributedTitle = AttributedString( + "Protected by Firefox", + attributes: AttributeContainer([.font: FXFontStyles.Regular.body.scaledFont()]) + ) + $0.configuration?.imagePadding = UX.privacyButtonImagePadding + $0.configuration?.contentInsets = UX.privacyButtonContentInset + $0.configuration?.cornerStyle = .fixed + $0.configuration?.background.cornerRadius = UX.privacyButtonCornerRadius } private let contentView: QuickAnswersContentView = .build() private let transitionAnimator: TransitionAnimator @@ -105,8 +141,20 @@ public final class QuickAnswersViewController: UIViewController, Themeable { registerViewModelUpdates() } + override public func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + contentView.adjustBottomInsets(for: privacyButton.frame.height) + } + private func setupSubviews() { - view.addSubviews(backgroundRecordEffect, backgroundBlur, audioWaveform, contentView, closeButton) + view.addSubviews( + backgroundRecordEffect, + backgroundBlur, + audioWaveform, + contentView, + closeButton, + privacyButton + ) NSLayoutConstraint.activate([ closeButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, @@ -121,11 +169,11 @@ public final class QuickAnswersViewController: UIViewController, Themeable { contentView.topAnchor.constraint(equalTo: audioWaveform.bottomAnchor, constant: UX.contentViewTopPadding), - contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor, + contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: UX.contentViewHorizontalPadding), - contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor, + contentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -UX.contentViewHorizontalPadding), - contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor, + contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -UX.contentViewBottomPadding), backgroundRecordEffect.widthAnchor.constraint(equalToConstant: UX.recordWaveEffectSize), @@ -133,6 +181,13 @@ public final class QuickAnswersViewController: UIViewController, Themeable { backgroundRecordEffect.centerXAnchor.constraint(equalTo: view.centerXAnchor), backgroundRecordEffect.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: UX.recordWaveEffectBottomPadding), + + privacyButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + privacyButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + privacyButton.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, + constant: UX.closeButtonSidePadding), + privacyButton.trailingAnchor.constraint(lessThanOrEqualTo: closeButton.leadingAnchor, + constant: -UX.closeButtonSidePadding), ]) backgroundBlur.pinToSuperview() } @@ -146,7 +201,8 @@ public final class QuickAnswersViewController: UIViewController, Themeable { self?.audioWaveform.stopAnimating() self?.contentView.configureSearching() case .showSearchResult(let result, _): - self?.contentView.configureAnswer(result.body) + self?.contentView.configureAnswer(result.resultText) + self?.contentView.configureSources(result.sources) } } viewModel.startRecordingVoice() @@ -158,6 +214,8 @@ public final class QuickAnswersViewController: UIViewController, Themeable { view.backgroundColor = theme.colors.layer2 closeButton.configuration?.baseBackgroundColor = theme.colors.layer2 closeButton.configuration?.baseForegroundColor = theme.colors.iconPrimary + privacyButton.configuration?.baseBackgroundColor = theme.colors.layerAccentPrivateNonOpaque + privacyButton.configuration?.baseForegroundColor = theme.colors.textPrimary backgroundRecordEffect.applyTheme(theme: theme) audioWaveform.applyTheme(theme: theme) contentView.applyTheme(theme: theme) diff --git a/BrowserKit/Sources/Shared/Prefs.swift b/BrowserKit/Sources/Shared/Prefs.swift index 6e08cc3dee8bf..76e741d194d03 100644 --- a/BrowserKit/Sources/Shared/Prefs.swift +++ b/BrowserKit/Sources/Shared/Prefs.swift @@ -112,6 +112,7 @@ public struct PrefsKeys { public struct HomepageSettings { public static let BookmarksSection = "BookmarksSectionUserPrefsKey" public static let JumpBackInSection = "JumpBackInSectionUserPrefsKey" + public static let WorldCupSection = "WorldCupSectionUserPrefsKey" } public struct SearchSettings { diff --git a/BrowserKit/Sources/SiteImageView/HeroImageViewModel.swift b/BrowserKit/Sources/SiteImageView/HeroImageViewModel.swift index d6cba999f5146..6fb6f9d30ed1b 100644 --- a/BrowserKit/Sources/SiteImageView/HeroImageViewModel.swift +++ b/BrowserKit/Sources/SiteImageView/HeroImageViewModel.swift @@ -14,21 +14,23 @@ public protocol HeroImageViewModel { var fallbackFaviconSize: CGSize { get } } -struct DefaultHeroImageViewModel: HeroImageViewModel { - var urlStringRequest: String - var type: SiteImageType - var generalCornerRadius: CGFloat - var faviconCornerRadius: CGFloat - var faviconBorderWidth: CGFloat - var heroImageSize: CGSize - var fallbackFaviconSize: CGSize +public struct DefaultHeroImageViewModel: HeroImageViewModel { + public var urlStringRequest: String + public var type: SiteImageType + public var generalCornerRadius: CGFloat + public var faviconCornerRadius: CGFloat + public var faviconBorderWidth: CGFloat + public var heroImageSize: CGSize + public var fallbackFaviconSize: CGSize - init(urlStringRequest: String, - generalCornerRadius: CGFloat, - faviconCornerRadius: CGFloat, - faviconBorderWidth: CGFloat, - heroImageSize: CGSize, - fallbackFaviconSize: CGSize) { + public init( + urlStringRequest: String, + generalCornerRadius: CGFloat, + faviconCornerRadius: CGFloat, + faviconBorderWidth: CGFloat, + heroImageSize: CGSize, + fallbackFaviconSize: CGSize + ) { self.type = .heroImage self.urlStringRequest = urlStringRequest self.generalCornerRadius = generalCornerRadius diff --git a/BrowserKit/Sources/SummarizeKit/Backend/SummarizerServiceFactory.swift b/BrowserKit/Sources/SummarizeKit/Backend/SummarizerServiceFactory.swift index fffd78f25ff9f..e1241d62349df 100644 --- a/BrowserKit/Sources/SummarizeKit/Backend/SummarizerServiceFactory.swift +++ b/BrowserKit/Sources/SummarizeKit/Backend/SummarizerServiceFactory.swift @@ -92,18 +92,17 @@ public struct DefaultSummarizerServiceFactory: SummarizerServiceFactory { return 0 } - // TODO: FXIOS-15146 - Add this to LLMKit and make creation more generic private func makeLiteLLMClient( config: SummarizerConfig, prefs: Prefs, isAppAttestAuthEnabled: Bool - ) -> LiteLLMClient? { + ) -> LiteLLMClientProtocol? { guard let model = config.options["model"] as? String, !model.isEmpty else { return nil } if isAppAttestAuthEnabled { - return LiteLLMCreator.createAppAttestLiteLLM(using: prefs) + return LiteLLMCreator().createAppAttestLiteLLM(using: prefs) } else { guard let endPoint = URL(string: LiteLLMConfig.apiEndpoint ?? ""), let key = LiteLLMConfig.apiKey else { diff --git a/BrowserKit/Tests/QuickAnswersKitTests/DefaultQuickAnswersServiceTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/DefaultQuickAnswersServiceTests.swift index 0c5ec19f34de7..526afbd23866d 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/DefaultQuickAnswersServiceTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/DefaultQuickAnswersServiceTests.swift @@ -2,23 +2,30 @@ // 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 Shared import Testing +import TestKit @testable import QuickAnswersKit @MainActor -struct DefaultQuickAnswersServiceTests { - let resultsService = MockResultsService() - // TODO: FXIOS-14891 Add more test to improve code coverage and check for memory leaks +class DefaultQuickAnswersServiceTests { + let testHelper = SwiftTestingHelper() + let engine = MockTranscriptionEngine() + let resultsServiceFactory: MockResultsServiceFactory + + init() { + resultsServiceFactory = MockResultsServiceFactory() + } + @Test func test_record_returnsExpectCallsAndResults() async throws { - let engine = MockTranscriptionEngine() engine.resultsToYield = [ SpeechResult(text: "What is the weather", isFinal: false), SpeechResult(text: "today?", isFinal: true) ] - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + let subject = try createSubject() let stream = try await subject.record() @@ -37,11 +44,10 @@ struct DefaultQuickAnswersServiceTests { } @Test - func test_record_throwsWhenPrepareFails() async { - let engine = MockTranscriptionEngine() + func test_record_throwsWhenPrepareFails() async throws { engine.prepareError = TestError.prepareFailed - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + let subject = try createSubject() do { _ = try await subject.record() @@ -56,10 +62,9 @@ struct DefaultQuickAnswersServiceTests { @Test func test_record_throwsWhenStartFails() async throws { - let engine = MockTranscriptionEngine() engine.startError = TestError.startFailed - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + let subject = try createSubject() let stream = try await subject.record() @@ -77,9 +82,7 @@ struct DefaultQuickAnswersServiceTests { @Test func test_stopRecording_callsExpectedMethods() async throws { - let engine = MockTranscriptionEngine() - - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + let subject = try createSubject() try await subject.stopRecording() @@ -87,11 +90,10 @@ struct DefaultQuickAnswersServiceTests { } @Test - func test_stopRecording_throwsWhenError() async { - let engine = MockTranscriptionEngine() + func test_stopRecording_throwsWhenError() async throws { engine.stopError = TestError.stopFailed - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + let subject = try createSubject() do { try await subject.stopRecording() @@ -104,9 +106,8 @@ struct DefaultQuickAnswersServiceTests { } @Test - func test_search_returnsEmptySuccess() async { - let engine = MockTranscriptionEngine() - let subject = DefaultQuickAnswersService(engine: engine, resultsService: resultsService) + func test_search_returnsEmptySuccess() async throws { + let subject = try createSubject() let result = await subject.search(text: "hello") @@ -117,4 +118,15 @@ struct DefaultQuickAnswersServiceTests { Issue.record("Expected success(.empty()), got failure: \(error)") } } + + // MARK: - Helper + private func createSubject() throws -> DefaultQuickAnswersService { + let subject = try DefaultQuickAnswersService( + engine: engine, + resultsServiceFactory: resultsServiceFactory, + prefs: MockProfilePrefs() + ) + testHelper.trackForMemoryLeaks(subject) + return subject + } } diff --git a/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockLLMClientCreator.swift b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockLLMClientCreator.swift new file mode 100644 index 0000000000000..8cb81beb78c40 --- /dev/null +++ b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockLLMClientCreator.swift @@ -0,0 +1,18 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 LLMKit +import Shared + +@testable import QuickAnswersKit + +final class MockLLMClientCreator: LiteLLMCreating { + var clientToReturn: LiteLLMClientProtocol? + var shouldReturnNil = false + var createAppAttestLiteLLMCallCount = 0 + + func createAppAttestLiteLLM(using prefs: Prefs) -> LiteLLMClientProtocol? { + createAppAttestLiteLLMCallCount += 1 + return shouldReturnNil ? nil : clientToReturn + } +} diff --git a/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsService.swift b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsService.swift index 7a2b327b7380d..779792222d238 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsService.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsService.swift @@ -6,18 +6,8 @@ final class MockResultsService: ResultsService, @unchecked Sendable { var fetchResultsCallCount = 0 - var lastTranscription: String? - var resultToReturn: SearchResult = .empty() - var errorToThrow: Error? func fetchResults(for transcription: String) async throws -> SearchResult { - fetchResultsCallCount += 1 - lastTranscription = transcription - - if let error = errorToThrow { - throw error - } - - return resultToReturn + return SearchResult.empty() } } diff --git a/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsServiceFactory.swift b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsServiceFactory.swift new file mode 100644 index 0000000000000..2492bb5eeddbe --- /dev/null +++ b/BrowserKit/Tests/QuickAnswersKitTests/Mocks/MockResultsServiceFactory.swift @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 Shared +@testable import QuickAnswersKit + +final class MockResultsServiceFactory: ResultsServiceFactory { + var makeCallCount = 0 + var shouldReturnNil = false + + func make(prefs: Prefs, config: QuickAnswersConfig) -> ResultsService? { + makeCallCount += 1 + + if shouldReturnNil { + return nil + } + + return MockResultsService() + } +} diff --git a/BrowserKit/Tests/QuickAnswersKitTests/QuickAnswersViewModelTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/QuickAnswersViewModelTests.swift index deb77f2fedd76..931c1abae806c 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/QuickAnswersViewModelTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/QuickAnswersViewModelTests.swift @@ -23,7 +23,14 @@ final class QuickAnswersViewModelTests: XCTestCase { func testStartRecordingVoice_receivesSpeechResult_triggersSearch() { let partialResult = SpeechResult(text: "Hello", isFinal: false) let finalResult = SpeechResult(text: "Hello world", isFinal: true) - let searchResult = SearchResult(title: "Test", body: "Body", url: nil) + let searchResult = SearchResult( + resultText: "Test", + sources: [SearchResult.Source( + title: "SourceTest", + thumbnailURL: nil, + faviconURL: nil + )] + ) mockService.speechResults = [partialResult, finalResult] mockService.searchResult = .success(searchResult) let expectation = XCTestExpectation() @@ -92,7 +99,14 @@ final class QuickAnswersViewModelTests: XCTestCase { func testStopRecordingVoice_withRecentResult_triggersSearch() { let finalResult = SpeechResult(text: "Hello", isFinal: true) - let searchResult = SearchResult(title: "Test", body: "Test", url: nil) + let searchResult = SearchResult( + resultText: "Test", + sources: [SearchResult.Source( + title: "SourceTest", + thumbnailURL: nil, + faviconURL: nil + )] + ) mockService.speechResults = [finalResult] mockService.searchResult = .success(searchResult) diff --git a/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/DefaultResultsServiceTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/DefaultResultsServiceTests.swift index 6dba618c30adc..519ea792f7ba2 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/DefaultResultsServiceTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/DefaultResultsServiceTests.swift @@ -12,36 +12,21 @@ struct DefaultResultsServiceTests { let testHelper = SwiftTestingHelper() @Test - func test_fetchResults_returnsResultFromSingleChunk() async throws { + func test_fetchResults_returnsResult() async throws { let client = MockLiteLLMClient() client.respondWith = ["This is a quick answer"] let subject = createSubject(client: client) let result = try await subject.fetchResults(for: "What is the weather?") - #expect(result.body == "This is a quick answer") - #expect(result.title == "Quick Answer") - #expect(client.requestChatCompletionStreamedCallCount == 1) + #expect(result.resultText == "This is a quick answer") + #expect(result.sources.count == 1) + #expect(client.requestChatCompletionCallCount == 1) #expect(client.lastMessages?.count == 1) #expect(client.lastMessages?.first?.content == "What is the weather?") #expect(client.lastMessages?.first?.role == .user) } - @Test - func test_fetchResults_accumulatesMultipleChunks() async throws { - let client = MockLiteLLMClient() - client.respondWith = ["The ", "weather ", "is ", "sunny"] - let subject = createSubject(client: client) - - let result = try await subject.fetchResults(for: "weather today") - - #expect(result.body == "The weather is sunny") - #expect(client.requestChatCompletionStreamedCallCount == 1) - #expect(client.lastMessages?.count == 1) - #expect(client.lastMessages?.first?.content == "weather today") - #expect(client.lastMessages?.first?.role == .user) - } - // MARK: - Helper private func createSubject( client: LiteLLMClientProtocol, diff --git a/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/ResultsServiceFactoryTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/ResultsServiceFactoryTests.swift index 7856ced26e015..3bc6065fcfd0c 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/ResultsServiceFactoryTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/ResultsService/ResultsServiceFactoryTests.swift @@ -3,29 +3,75 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import LLMKit +import MLPAKit +import Shared import Testing import TestKit @testable import QuickAnswersKit struct ResultsServiceFactoryTests { - // TODO: FXIOS-15196 - Improve testing to be more valuable @Test - func test_make_storesConfig() { - let config = QuickAnswersConfig() - let subject = createSubject(config: config) + func test_make_withValidConfig_returnsConfiguredService() { + let mockLLMCreator = MockLLMClientCreator() + mockLLMCreator.clientToReturn = MockLiteLLMClient() + let prefs = MockProfilePrefs() + let config = QuickAnswersConfig(options: ["model": "test-model"]) + let subject = createSubject(liteLLMCreator: mockLLMCreator, config: config) - _ = subject.make() + let result = subject.make(prefs: prefs, config: config) - #expect(subject.config.instructions.isEmpty) - #expect(subject.config.options.isEmpty) + #expect(result != nil, "Factory should return configured service when LLM client is available") + #expect(mockLLMCreator.createAppAttestLiteLLMCallCount == 1, "Should call createAppAttestLiteLLM once") + } + + @Test + func test_make_withNoModelOption_returnsNil() { + let mockLLMCreator = MockLLMClientCreator() + mockLLMCreator.clientToReturn = MockLiteLLMClient() + let prefs = MockProfilePrefs() + let config = QuickAnswersConfig(options: [:]) + let subject = createSubject(liteLLMCreator: mockLLMCreator, config: config) + + let result = subject.make(prefs: prefs, config: config) + + #expect(result == nil, "Factory should return nil when config is missing model options") + #expect(mockLLMCreator.createAppAttestLiteLLMCallCount == 0, "Should not call when model is missing") + } + + @Test + func test_make_withEmptyModel_returnsNil() { + let mockLLMCreator = MockLLMClientCreator() + mockLLMCreator.clientToReturn = MockLiteLLMClient() + let prefs = MockProfilePrefs() + let config = QuickAnswersConfig(options: ["model": ""]) + let subject = createSubject(liteLLMCreator: mockLLMCreator, config: config) + + let result = subject.make(prefs: prefs, config: config) + + #expect(result == nil, "Factory should return nil when model is empty") + #expect(mockLLMCreator.createAppAttestLiteLLMCallCount == 0, "Should not call when model is empty") + } + + @Test + func test_make_withNilLLMClient_returnsNil() { + let mockLLMCreator = MockLLMClientCreator() + mockLLMCreator.shouldReturnNil = true + let prefs = MockProfilePrefs() + let config = QuickAnswersConfig(options: ["model": "test-model"]) + let subject = createSubject(liteLLMCreator: mockLLMCreator, config: config) + + let result = subject.make(prefs: prefs, config: config) + + #expect(result == nil, "Factory should return nil when LLM client creation fails") + #expect(mockLLMCreator.createAppAttestLiteLLMCallCount == 1, "Should attempt to create LLM client") } // MARK: - Helper private func createSubject( + liteLLMCreator: LiteLLMCreating = MockLLMClientCreator(), config: QuickAnswersConfig = QuickAnswersConfig() ) -> DefaultResultsServiceFactory { - let subject = DefaultResultsServiceFactory(config: config) - return subject + return DefaultResultsServiceFactory(config: config, liteLLMCreator: liteLLMCreator) } } diff --git a/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/AudioManagerTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/AudioManagerTests.swift index 97e781b2d204a..b7d6afec46f52 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/AudioManagerTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/AudioManagerTests.swift @@ -10,9 +10,9 @@ import TestKit @Suite struct AudioManagerTests { + let testHelper = SwiftTestingHelper() let session = MockAudioSession() let engine = MockAudioEngine() - let testHelper = SwiftTestingHelper() // MARK: - Audio Session Configuration Tests @Test diff --git a/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/SpeechAnalyzerEngineTests.swift b/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/SpeechAnalyzerEngineTests.swift index b6583bc463d15..ba77b22669616 100644 --- a/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/SpeechAnalyzerEngineTests.swift +++ b/BrowserKit/Tests/QuickAnswersKitTests/SpeechService/SpeechAnalyzerEngineTests.swift @@ -11,15 +11,12 @@ import TestKit @Suite @MainActor struct SpeechAnalyzerEngineTests { - let audioManager = MockAudioManager() let testHelper = SwiftTestingHelper() + let audioManager = MockAudioManager() + @available(iOS 26.0, *) @Test - func test_prepare_microphoneDenied_speechDenied_throwsError() async { - guard #available(iOS 26.0, *) else { - return - } - + func test_prepare_microphoneDenied_speechDenied_throwsError() async throws { let authorizer = MockAuthorizer(micAuthorized: false, speechAuthorized: false) let subject = createSubject(authorizer: authorizer) @@ -30,12 +27,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 0) } + @available(iOS 26.0, *) @Test - func test_prepare_microphoneDenied_speechGranted_throwsError() async { - guard #available(iOS 26.0, *) else { - return - } - + func test_prepare_microphoneDenied_speechGranted_throwsError() async throws { let authorizer = MockAuthorizer(micAuthorized: false, speechAuthorized: true) let subject = createSubject(authorizer: authorizer) @@ -46,12 +40,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 0) } + @available(iOS 26.0, *) @Test func test_prepare_microphoneGranted_speechDenied_throwsError() async throws { - guard #available(iOS 26.0, *) else { - return - } - let authorizer = MockAuthorizer(micAuthorized: true, speechAuthorized: false) let subject = createSubject(authorizer: authorizer) @@ -60,12 +51,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 1) } + @available(iOS 26.0, *) @Test func test_prepare_microphoneGranted_speechGranted_throwsError() async throws { - guard #available(iOS 26.0, *) else { - return - } - let authorizer = MockAuthorizer(micAuthorized: true, speechAuthorized: true) let subject = createSubject(authorizer: authorizer) @@ -74,12 +62,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 1) } + @available(iOS 26.0, *) @Test func test_prepare_withPermissions_callsConfigureAudioSession() async throws { - guard #available(iOS 26.0, *) else { - return - } - let authorizer = MockAuthorizer(micAuthorized: true, speechAuthorized: true) let subject = createSubject(authorizer: authorizer) @@ -88,12 +73,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 1) } + @available(iOS 26.0, *) @Test func test_prepare_withPermissions_throwsError() async throws { - guard #available(iOS 26.0, *) else { - return - } - let authorizer = MockAuthorizer(micAuthorized: true, speechAuthorized: true) let subject = createSubject(authorizer: authorizer) audioManager.shouldThrowOnConfigure = true @@ -105,11 +87,9 @@ struct SpeechAnalyzerEngineTests { #expect(audioManager.configureAudioSessionCallCount == 1) } + @available(iOS 26.0, *) @Test func test_stop_callsStopEngine() async throws { - guard #available(iOS 26.0, *) else { - return - } let authorizer = MockAuthorizer(micAuthorized: true, speechAuthorized: true) let subject = createSubject(authorizer: authorizer) diff --git a/Dangerfile.swift b/Dangerfile.swift index 2cd4db276a17e..8dfa4f59813c4 100644 --- a/Dangerfile.swift +++ b/Dangerfile.swift @@ -436,6 +436,12 @@ class CodeUsageDetector { "MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/" ] + private struct Detection { + let keyword: Keywords + let file: String + let lineNumber: Int + } + private enum Keywords: CaseIterable { static let commonLoggerSentence = " Please remove this usage from production code or use BrowserKit Logger." @@ -447,28 +453,23 @@ class CodeUsageDetector { case task case notifiable - var message: String { + var bundledHeader: String { switch self { case .print: - return "Print() function seems to be used in file %@ at line %d.\(Keywords.commonLoggerSentence)" + return "### ⚠️ `print()` usage detected\nPrint() function seems to be used.\(Keywords.commonLoggerSentence)" case .nsLog: - return "NSLog() function seems to be used in file %@ at line %d.\(Keywords.commonLoggerSentence)" + return "### ⚠️ `NSLog()` usage detected\nNSLog() function seems to be used.\(Keywords.commonLoggerSentence)" case .osLog: - return "os_log() function seems to be used in file %@ at line %d.\(Keywords.commonLoggerSentence)" + return "### ⚠️ `os_log()` usage detected\nos_log() function seems to be used.\(Keywords.commonLoggerSentence)" case .deferred: - return "Deferred class is used in file %@ at line %d. Please replace with completion handler instead." + return "### ⚠️ `Deferred` usage detected\nDeferred class is used. Please replace with completion handler instead." case .swiftUIText: - return "SwiftUI 'Text(\"\"' in file %@ at line %d needs to be avoided, use Strings.swift localization instead." + return "### ⚠️ SwiftUI `Text(\"\")` usage detected\nSwiftUI 'Text(\"\"' needs to be avoided, use Strings.swift localization instead." case .task: let contacts = "@Cramsden @ih-codes @lmarceau" - return """ - ### 🧑‍💻 New `Task {}` detected - New `Task {}` added in file %@ at line %d. - Please add a concurrency reviewer on your PR: \(contacts) - """ + return "### 🧑‍💻 New `Task {}` detected\nNew `Task {}` added. Please add a concurrency reviewer on your PR: \(contacts)" case .notifiable: - let usage = "Please prefer Notifiable over `NotificationCenter` unless specific circumstances." - return "`NotificationCenter.default.addObserver` detected in file %@ at line %d. \(usage)" + return "### ⚠️ `NotificationCenter.default.addObserver` detected\nPlease prefer Notifiable over `NotificationCenter` unless specific circumstances." } } @@ -520,6 +521,7 @@ class CodeUsageDetector { } func checkForCodeUsage() { + var detections: [Detection] = [] let editedFiles = danger.git.modifiedFiles + danger.git.createdFiles // Iterate through each added and modified file, running the checks on swift files only for file in editedFiles where file.contains(".swift") && !file.contains("Dangerfile") { @@ -529,12 +531,11 @@ class CodeUsageDetector { if file == BrowserViewControllerChecker.bvcPath { BrowserViewControllerChecker().checkBrowserViewControllerSize(fileDiff: diff) } - switch diff.changes { case let .modified(hunks), let .renamed(_, hunks): - detect(keywords: Keywords.allCases, inHunks: hunks, file: file) + detections += collect(keywords: Keywords.allCases, inHunks: hunks, file: file) case let .created(newLines): - detect(keywords: Keywords.allCases, inLines: newLines, file: file) + detections += collect(keywords: Keywords.allCases, inLines: newLines, file: file) case .deleted: break // do not warn on deleted lines } @@ -542,16 +543,40 @@ class CodeUsageDetector { break } } + + // Group by keyword and emit one bundled message per keyword + let grouped = Dictionary(grouping: detections, by: { $0.keyword }) + for keyword in Keywords.allCases { + guard let items = grouped[keyword], !items.isEmpty else { continue } + emitBundled(keyword: keyword, detections: items) + } } - private func detect(keywords: [Keywords], inHunks hunks: [FileDiff.Hunk], file: String) { - for keyword in keywords { - detect(keyword: keyword, inHunks: hunks, file: file, message: keyword.message) + private func emitBundled(keyword: Keywords, detections: [Detection]) { + let rows = detections.map { "| `\($0.file)` | \($0.lineNumber) |" }.joined(separator: "\n") + let fullMessage = """ + \(keyword.bundledHeader) + + | File | Line | + |---|---| + \(rows) + """ + if keyword.shouldComment { + markdown(fullMessage) + } else if keyword.shouldWarn { + warn(fullMessage) + } else { + failOrWarn(fullMessage) } } - private func detect(keyword: Keywords, inHunks hunks: [FileDiff.Hunk], file: String, message: String) { - guard !shouldSkip(keyword, for: file) else { return } + private func collect(keywords: [Keywords], inHunks hunks: [FileDiff.Hunk], file: String) -> [Detection] { + keywords.flatMap { collect(keyword: $0, inHunks: hunks, file: file) } + } + + private func collect(keyword: Keywords, inHunks hunks: [FileDiff.Hunk], file: String) -> [Detection] { + guard !shouldSkip(keyword, for: file) else { return [] } + var detections: [Detection] = [] for hunk in hunks { var newLineCount = 0 for line in hunk.lines { @@ -560,39 +585,24 @@ class CodeUsageDetector { // Make sure our newLineCount is proper to fail on correct line number guard isAddedLine || !isRemovedLine else { continue } newLineCount += 1 - - // Fail only on added line having the particular keyword + // Collect only added lines having the particular keyword guard isAddedLine && String(describing: line).contains(keyword.keyword) else { continue } - let lineNumber = hunk.newLineStart + newLineCount - 1 - if keyword.shouldComment { - markdown(String(format: message, file, lineNumber)) - } else if keyword.shouldWarn { - warn(String(format: message, file, lineNumber)) - } else { - failOrWarn(String(format: message, file, lineNumber)) - } + detections.append(Detection(keyword: keyword, file: file, lineNumber: lineNumber)) } } + return detections } - private func detect(keywords: [Keywords], inLines lines: [String], file: String) { - for keyword in keywords { - detect(keyword: keyword, inLines: lines, file: file, message: keyword.message) - } + private func collect(keywords: [Keywords], inLines lines: [String], file: String) -> [Detection] { + keywords.flatMap { collect(keyword: $0, inLines: lines, file: file) } } - private func detect(keyword: Keywords, inLines lines: [String], file: String, message: String) { - guard !shouldSkip(keyword, for: file) else { return } - for (index, line) in lines.enumerated() where line.contains(keyword.keyword) { - let lineNumber = index + 1 - if keyword.shouldComment { - markdown(String(format: message, file, lineNumber)) - } else if keyword.shouldWarn { - warn(String(format: message, file, lineNumber)) - } else { - failOrWarn(String(format: message, file, lineNumber)) - } + private func collect(keyword: Keywords, inLines lines: [String], file: String) -> [Detection] { + guard !shouldSkip(keyword, for: file) else { return [] } + return lines.enumerated().compactMap { index, line in + guard line.contains(keyword.keyword) else { return nil } + return Detection(keyword: keyword, file: file, lineNumber: index + 1) } } } diff --git a/MozillaRustComponents/Package.swift b/MozillaRustComponents/Package.swift index 4e0e0a121d4e0..419ebf32f5a7c 100644 --- a/MozillaRustComponents/Package.swift +++ b/MozillaRustComponents/Package.swift @@ -1,13 +1,13 @@ // swift-tools-version: 5.10 import PackageDescription -let checksum = "733b0bce006dbed52584baa5aaf4983bb47c7cd93975ed17e085677335d243d5" -let version = "151.0.20260414050221" -let url = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.151.20260414050221/artifacts/public/build/MozillaRustComponents.xcframework.zip" +let checksum = "22fcc61b8c3def6d862452a7b62972bf129d64df9c4acea308771b104529cfb5" +let version = "152.0.20260422050218" +let url = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.152.20260422050218/artifacts/public/build/MozillaRustComponents.xcframework.zip" // Focus xcframework -let focusChecksum = "ec4d35c7aa3810e7da3f7064d18894d57bb89b00be0275adeeea5db9bc460d5d" -let focusUrl = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.151.20260414050221/artifacts/public/build/FocusRustComponents.xcframework.zip" +let focusChecksum = "6505d17a29d620fb535edad686bb0306db33ce567001382b404b2209de2bc48b" +let focusUrl = "https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.swift.152.20260422050218/artifacts/public/build/FocusRustComponents.xcframework.zip" let package = Package( name: "MozillaRustComponentsSwift", diff --git a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/Metrics/Metrics.swift b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/Metrics/Metrics.swift index 6353b7eb2308a..50326c780be8e 100644 --- a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/Metrics/Metrics.swift +++ b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/Metrics/Metrics.swift @@ -1,6 +1,6 @@ // -*- mode: Swift -*- -// AUTOGENERATED BY glean_parser v18.2.0. DO NOT EDIT. DO NOT COMMIT. +// AUTOGENERATED BY glean_parser v19.0.0. DO NOT EDIT. DO NOT COMMIT. #if canImport(Foundation) import Foundation @@ -23,7 +23,7 @@ extension GleanMetrics { // Intentionally left private, no external user can instantiate a new global object. } - public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2026, month: 4, day: 14, hour: 5, minute: 32, second: 32)) + public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2026, month: 4, day: 22, hour: 5, minute: 35, second: 57)) } enum NimbusEvents { diff --git a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/ads_client.swift b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/ads_client.swift index 7d53b119c390c..93df46f7b4126 100644 --- a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/ads_client.swift +++ b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/ads_client.swift @@ -470,6 +470,30 @@ fileprivate struct FfiConverterDouble: FfiConverterPrimitive { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -518,11 +542,11 @@ public protocol MozAdsClientProtocol: AnyObject, Sendable { func clearCache() throws - func recordClick(clickUrl: String) throws + func recordClick(clickUrl: String, options: MozAdsCallbackOptions?) throws - func recordImpression(impressionUrl: String) throws + func recordImpression(impressionUrl: String, options: MozAdsCallbackOptions?) throws - func reportAd(reportUrl: String, reason: MozAdsReportReason) throws + func reportAd(reportUrl: String, reason: MozAdsReportReason, options: MozAdsCallbackOptions?) throws func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?) throws -> [String: MozAdsImage] @@ -591,32 +615,35 @@ open func clearCache()throws {try rustCallWithError(FfiConverterTypeMozAdsClie } } -open func recordClick(clickUrl: String)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func recordClick(clickUrl: String, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_record_click( self.uniffiCloneHandle(), - FfiConverterString.lower(clickUrl),$0 + FfiConverterString.lower(clickUrl), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func recordImpression(impressionUrl: String)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func recordImpression(impressionUrl: String, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_record_impression( self.uniffiCloneHandle(), - FfiConverterString.lower(impressionUrl),$0 + FfiConverterString.lower(impressionUrl), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func reportAd(reportUrl: String, reason: MozAdsReportReason)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func reportAd(reportUrl: String, reason: MozAdsReportReason, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_report_ad( self.uniffiCloneHandle(), FfiConverterString.lower(reportUrl), - FfiConverterTypeMozAdsReportReason_lower(reason),$0 + FfiConverterTypeMozAdsReportReason_lower(reason), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?)throws -> [String: MozAdsImage] { +open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions? = nil)throws -> [String: MozAdsImage] { return try FfiConverterDictionaryStringTypeMozAdsImage.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_image_ads( self.uniffiCloneHandle(), @@ -626,7 +653,7 @@ open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozA }) } -open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], options: MozAdsRequestOptions?)throws -> [String: [MozAdsSpoc]] { +open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], options: MozAdsRequestOptions? = nil)throws -> [String: [MozAdsSpoc]] { return try FfiConverterDictionaryStringSequenceTypeMozAdsSpoc.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_spoc_ads( self.uniffiCloneHandle(), @@ -636,7 +663,7 @@ open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], optio }) } -open func requestTileAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?)throws -> [String: MozAdsTile] { +open func requestTileAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions? = nil)throws -> [String: MozAdsTile] { return try FfiConverterDictionaryStringTypeMozAdsTile.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_tile_ads( self.uniffiCloneHandle(), @@ -1491,6 +1518,56 @@ public func FfiConverterTypeMozAdsCachePolicy_lower(_ value: MozAdsCachePolicy) } +public struct MozAdsCallbackOptions: Equatable, Hashable { + public var ohttp: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(ohttp: Bool = false) { + self.ohttp = ohttp + } + + + + +} + +#if compiler(>=6) +extension MozAdsCallbackOptions: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMozAdsCallbackOptions: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MozAdsCallbackOptions { + return + try MozAdsCallbackOptions( + ohttp: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: MozAdsCallbackOptions, into buf: inout [UInt8]) { + FfiConverterBool.write(value.ohttp, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMozAdsCallbackOptions_lift(_ buf: RustBuffer) throws -> MozAdsCallbackOptions { + return try FfiConverterTypeMozAdsCallbackOptions.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMozAdsCallbackOptions_lower(_ value: MozAdsCallbackOptions) -> RustBuffer { + return FfiConverterTypeMozAdsCallbackOptions.lower(value) +} + + public struct MozAdsCallbacks: Equatable, Hashable { public var click: AdsClientUrl public var impression: AdsClientUrl @@ -1841,11 +1918,13 @@ public func FfiConverterTypeMozAdsPlacementRequestWithCount_lower(_ value: MozAd public struct MozAdsRequestOptions: Equatable, Hashable { public var cachePolicy: MozAdsCachePolicy? + public var ohttp: Bool // Default memberwise initializers are never public by default, so we // declare one manually. - public init(cachePolicy: MozAdsCachePolicy?) { + public init(cachePolicy: MozAdsCachePolicy?, ohttp: Bool = false) { self.cachePolicy = cachePolicy + self.ohttp = ohttp } @@ -1864,12 +1943,14 @@ public struct FfiConverterTypeMozAdsRequestOptions: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MozAdsRequestOptions { return try MozAdsRequestOptions( - cachePolicy: FfiConverterOptionTypeMozAdsCachePolicy.read(from: &buf) + cachePolicy: FfiConverterOptionTypeMozAdsCachePolicy.read(from: &buf), + ohttp: FfiConverterBool.read(from: &buf) ) } public static func write(_ value: MozAdsRequestOptions, into buf: inout [UInt8]) { FfiConverterOptionTypeMozAdsCachePolicy.write(value.cachePolicy, into: &buf) + FfiConverterBool.write(value.ohttp, into: &buf) } } @@ -2606,6 +2687,30 @@ fileprivate struct FfiConverterOptionTypeMozAdsCachePolicy: FfiConverterRustBuff } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeMozAdsCallbackOptions: FfiConverterRustBuffer { + typealias SwiftType = MozAdsCallbackOptions? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeMozAdsCallbackOptions.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeMozAdsCallbackOptions.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -2941,25 +3046,25 @@ private let initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_clear_cache() != 63953) { + if (uniffi_ads_client_checksum_method_mozadsclient_clear_cache() != 10112) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_record_click() != 2) { + if (uniffi_ads_client_checksum_method_mozadsclient_record_click() != 59910) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_record_impression() != 43275) { + if (uniffi_ads_client_checksum_method_mozadsclient_record_impression() != 57294) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_report_ad() != 18252) { + if (uniffi_ads_client_checksum_method_mozadsclient_report_ad() != 56767) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_image_ads() != 2157) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_image_ads() != 20861) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_spoc_ads() != 37780) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_spoc_ads() != 2130) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_tile_ads() != 26296) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_tile_ads() != 10) { return InitializationResult.apiChecksumMismatch } if (uniffi_ads_client_checksum_method_mozadsclientbuilder_build() != 36609) { diff --git a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/remote_settings.swift b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/remote_settings.swift index a22fe8dd706c0..ab7d0083a1675 100644 --- a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/remote_settings.swift +++ b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/remote_settings.swift @@ -518,175 +518,6 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer { -public protocol RemoteSettingsProtocol: AnyObject, Sendable { - - /** - * Download an attachment with the provided id to the provided path. - */ - func downloadAttachmentToPath(attachmentId: String, path: String) throws - - /** - * Fetch all records for the configuration this client was initialized with. - */ - func getRecords() throws -> RemoteSettingsResponse - - /** - * Fetch all records added to the server since the provided timestamp, - * using the configuration this client was initialized with. - */ - func getRecordsSince(timestamp: UInt64) throws -> RemoteSettingsResponse - -} -open class RemoteSettings: RemoteSettingsProtocol, @unchecked Sendable { - fileprivate let handle: UInt64 - - /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public struct NoHandle { - public init() {} - } - - // TODO: We'd like this to be `private` but for Swifty reasons, - // we can't implement `FfiConverter` without making this `required` and we can't - // make it `required` without making it `public`. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - required public init(unsafeFromHandle handle: UInt64) { - self.handle = handle - } - - // This constructor can be used to instantiate a fake object. - // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. - // - // - Warning: - // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public init(noHandle: NoHandle) { - self.handle = 0 - } - -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public func uniffiCloneHandle() -> UInt64 { - return try! rustCall { uniffi_remote_settings_fn_clone_remotesettings(self.handle, $0) } - } - /** - * Construct a new Remote Settings client with the given configuration. - */ -public convenience init(remoteSettingsConfig: RemoteSettingsConfig)throws { - let handle = - try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_constructor_remotesettings_new( - FfiConverterTypeRemoteSettingsConfig_lower(remoteSettingsConfig),$0 - ) -} - self.init(unsafeFromHandle: handle) -} - - deinit { - if handle == 0 { - // Mock objects have handle=0 don't try to free them - return - } - - try! rustCall { uniffi_remote_settings_fn_free_remotesettings(handle, $0) } - } - - - - - /** - * Download an attachment with the provided id to the provided path. - */ -open func downloadAttachmentToPath(attachmentId: String, path: String)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_download_attachment_to_path( - self.uniffiCloneHandle(), - FfiConverterString.lower(attachmentId), - FfiConverterString.lower(path),$0 - ) -} -} - - /** - * Fetch all records for the configuration this client was initialized with. - */ -open func getRecords()throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse_lift(try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_get_records( - self.uniffiCloneHandle(),$0 - ) -}) -} - - /** - * Fetch all records added to the server since the provided timestamp, - * using the configuration this client was initialized with. - */ -open func getRecordsSince(timestamp: UInt64)throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse_lift(try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_get_records_since( - self.uniffiCloneHandle(), - FfiConverterUInt64.lower(timestamp),$0 - ) -}) -} - - - -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettings: FfiConverter { - typealias FfiType = UInt64 - typealias SwiftType = RemoteSettings - - public static func lift(_ handle: UInt64) throws -> RemoteSettings { - return RemoteSettings(unsafeFromHandle: handle) - } - - public static func lower(_ value: RemoteSettings) -> UInt64 { - return value.uniffiCloneHandle() - } - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettings { - let handle: UInt64 = try readInt(&buf) - return try lift(handle) - } - - public static func write(_ value: RemoteSettings, into buf: inout [UInt8]) { - writeInt(&buf, lower(value)) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettings_lift(_ handle: UInt64) throws -> RemoteSettings { - return try FfiConverterTypeRemoteSettings.lift(handle) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettings_lower(_ value: RemoteSettings) -> UInt64 { - return FfiConverterTypeRemoteSettings.lower(value) -} - - - - - - /** * Client for a single Remote Settings collection * @@ -712,6 +543,11 @@ public protocol RemoteSettingsClientProtocol: AnyObject, Sendable { */ func getAttachment(record: RemoteSettingsRecord) throws -> Data + /** + * Returns the last_modified value for the collection as an unsigned int64. + */ + func getLastModifiedTimestamp() -> UInt64? + /** * Get the current set of records. * @@ -837,6 +673,17 @@ open func getAttachment(record: RemoteSettingsRecord)throws -> Data { FfiConverterTypeRemoteSettingsRecord_lower(record),$0 ) }) +} + + /** + * Returns the last_modified value for the collection as an unsigned int64. + */ +open func getLastModifiedTimestamp() -> UInt64? { + return try! FfiConverterOptionUInt64.lift(try! rustCall() { + uniffi_remote_settings_fn_method_remotesettingsclient_get_last_modified_timestamp( + self.uniffiCloneHandle(),$0 + ) +}) } /** @@ -989,7 +836,7 @@ public protocol RemoteSettingsServiceProtocol: AnyObject, Sendable { * Only intended for QA/debugging. Swapping the remote settings server in the middle of * execution can cause weird effects. */ - func updateConfig(config: RemoteSettingsConfig2) throws + func updateConfig(config: RemoteSettingsConfig) throws } /** @@ -1049,12 +896,12 @@ open class RemoteSettingsService: RemoteSettingsServiceProtocol, @unchecked Send * directory does not exist, it will be created when the storage is first used. Only the * directory and the SQLite files will be created, any parent directories must already exist. */ -public convenience init(storageDir: String, config: RemoteSettingsConfig2) { +public convenience init(storageDir: String, config: RemoteSettingsConfig) { let handle = try! rustCall() { uniffi_remote_settings_fn_constructor_remotesettingsservice_new( FfiConverterString.lower(storageDir), - FfiConverterTypeRemoteSettingsConfig2_lower(config),$0 + FfiConverterTypeRemoteSettingsConfig_lower(config),$0 ) } self.init(unsafeFromHandle: handle) @@ -1117,10 +964,10 @@ open func sync()throws -> [String] { * Only intended for QA/debugging. Swapping the remote settings server in the middle of * execution can cause weird effects. */ -open func updateConfig(config: RemoteSettingsConfig2)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { +open func updateConfig(config: RemoteSettingsConfig)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { uniffi_remote_settings_fn_method_remotesettingsservice_update_config( self.uniffiCloneHandle(), - FfiConverterTypeRemoteSettingsConfig2_lower(config),$0 + FfiConverterTypeRemoteSettingsConfig_lower(config),$0 ) } } @@ -1243,84 +1090,10 @@ public func FfiConverterTypeAttachment_lower(_ value: Attachment) -> RustBuffer } -/** - * Custom configuration for the client. - * Currently includes the following: - * - `server`: The Remote Settings server to use. If not specified, defaults to the production server (`RemoteSettingsServer::Prod`). - * - `server_url`: An optional custom Remote Settings server URL. Deprecated; please use `server` instead. - * - `bucket_name`: The optional name of the bucket containing the collection on the server. If not specified, the standard bucket will be used. - * - `collection_name`: The name of the collection for the settings server. - */ -public struct RemoteSettingsConfig: Equatable, Hashable { - public var collectionName: String - public var bucketName: String? - public var serverUrl: String? - public var server: RemoteSettingsServer? - - // Default memberwise initializers are never public by default, so we - // declare one manually. - public init(collectionName: String, bucketName: String? = nil, serverUrl: String? = nil, server: RemoteSettingsServer? = nil) { - self.collectionName = collectionName - self.bucketName = bucketName - self.serverUrl = serverUrl - self.server = server - } - - - - -} - -#if compiler(>=6) -extension RemoteSettingsConfig: Sendable {} -#endif - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettingsConfig: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig { - return - try RemoteSettingsConfig( - collectionName: FfiConverterString.read(from: &buf), - bucketName: FfiConverterOptionString.read(from: &buf), - serverUrl: FfiConverterOptionString.read(from: &buf), - server: FfiConverterOptionTypeRemoteSettingsServer.read(from: &buf) - ) - } - - public static func write(_ value: RemoteSettingsConfig, into buf: inout [UInt8]) { - FfiConverterString.write(value.collectionName, into: &buf) - FfiConverterOptionString.write(value.bucketName, into: &buf) - FfiConverterOptionString.write(value.serverUrl, into: &buf) - FfiConverterOptionTypeRemoteSettingsServer.write(value.server, into: &buf) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsConfig_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig { - return try FfiConverterTypeRemoteSettingsConfig.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsConfig_lower(_ value: RemoteSettingsConfig) -> RustBuffer { - return FfiConverterTypeRemoteSettingsConfig.lower(value) -} - - /** * Remote settings configuration - * - * This is the version used in the new API, hence the `2` at the end. The plan is to move - * consumers to the new API, remove the RemoteSettingsConfig struct, then remove the `2` from this - * name. */ -public struct RemoteSettingsConfig2: Equatable, Hashable { +public struct RemoteSettingsConfig: Equatable, Hashable { /** * The Remote Settings server to use. Defaults to [RemoteSettingsServer::Prod], */ @@ -1357,23 +1130,23 @@ public struct RemoteSettingsConfig2: Equatable, Hashable { } #if compiler(>=6) -extension RemoteSettingsConfig2: Sendable {} +extension RemoteSettingsConfig: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeRemoteSettingsConfig2: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig2 { +public struct FfiConverterTypeRemoteSettingsConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig { return - try RemoteSettingsConfig2( + try RemoteSettingsConfig( server: FfiConverterOptionTypeRemoteSettingsServer.read(from: &buf), bucketName: FfiConverterOptionString.read(from: &buf), appContext: FfiConverterOptionTypeRemoteSettingsContext.read(from: &buf) ) } - public static func write(_ value: RemoteSettingsConfig2, into buf: inout [UInt8]) { + public static func write(_ value: RemoteSettingsConfig, into buf: inout [UInt8]) { FfiConverterOptionTypeRemoteSettingsServer.write(value.server, into: &buf) FfiConverterOptionString.write(value.bucketName, into: &buf) FfiConverterOptionTypeRemoteSettingsContext.write(value.appContext, into: &buf) @@ -1384,15 +1157,15 @@ public struct FfiConverterTypeRemoteSettingsConfig2: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeRemoteSettingsConfig2_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig2 { - return try FfiConverterTypeRemoteSettingsConfig2.lift(buf) +public func FfiConverterTypeRemoteSettingsConfig_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig { + return try FfiConverterTypeRemoteSettingsConfig.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeRemoteSettingsConfig2_lower(_ value: RemoteSettingsConfig2) -> RustBuffer { - return FfiConverterTypeRemoteSettingsConfig2.lower(value) +public func FfiConverterTypeRemoteSettingsConfig_lower(_ value: RemoteSettingsConfig) -> RustBuffer { + return FfiConverterTypeRemoteSettingsConfig.lower(value) } @@ -1636,64 +1409,6 @@ public func FfiConverterTypeRemoteSettingsRecord_lower(_ value: RemoteSettingsRe } -/** - * Data structure representing the top-level response from the Remote Settings. - * [last_modified] will be extracted from the etag header of the response. - */ -public struct RemoteSettingsResponse: Equatable, Hashable { - public var records: [RemoteSettingsRecord] - public var lastModified: UInt64 - - // Default memberwise initializers are never public by default, so we - // declare one manually. - public init(records: [RemoteSettingsRecord], lastModified: UInt64) { - self.records = records - self.lastModified = lastModified - } - - - - -} - -#if compiler(>=6) -extension RemoteSettingsResponse: Sendable {} -#endif - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettingsResponse: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsResponse { - return - try RemoteSettingsResponse( - records: FfiConverterSequenceTypeRemoteSettingsRecord.read(from: &buf), - lastModified: FfiConverterUInt64.read(from: &buf) - ) - } - - public static func write(_ value: RemoteSettingsResponse, into buf: inout [UInt8]) { - FfiConverterSequenceTypeRemoteSettingsRecord.write(value.records, into: &buf) - FfiConverterUInt64.write(value.lastModified, into: &buf) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsResponse_lift(_ buf: RustBuffer) throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsResponse_lower(_ value: RemoteSettingsResponse) -> RustBuffer { - return FfiConverterTypeRemoteSettingsResponse.lower(value) -} - - /** * Public error class, this is what we return to consumers */ @@ -1883,6 +1598,30 @@ public func FfiConverterTypeRemoteSettingsServer_lower(_ value: RemoteSettingsSe } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionUInt64: FfiConverterRustBuffer { + typealias SwiftType = UInt64? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt64.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt64.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -2212,21 +1951,15 @@ private let initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_remote_settings_checksum_method_remotesettings_download_attachment_to_path() != 64720) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_method_remotesettings_get_records() != 36167) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_method_remotesettings_get_records_since() != 45558) { - return InitializationResult.apiChecksumMismatch - } if (uniffi_remote_settings_checksum_method_remotesettingsclient_collection_name() != 54184) { return InitializationResult.apiChecksumMismatch } if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_attachment() != 10695) { return InitializationResult.apiChecksumMismatch } + if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_last_modified_timestamp() != 46461) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_records() != 52048) { return InitializationResult.apiChecksumMismatch } @@ -2251,13 +1984,10 @@ private let initializationResult: InitializationResult = { if (uniffi_remote_settings_checksum_method_remotesettingsservice_sync() != 41684) { return InitializationResult.apiChecksumMismatch } - if (uniffi_remote_settings_checksum_method_remotesettingsservice_update_config() != 29011) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_constructor_remotesettings_new() != 20240) { + if (uniffi_remote_settings_checksum_method_remotesettingsservice_update_config() != 23848) { return InitializationResult.apiChecksumMismatch } - if (uniffi_remote_settings_checksum_constructor_remotesettingsservice_new() != 58353) { + if (uniffi_remote_settings_checksum_constructor_remotesettingsservice_new() != 24841) { return InitializationResult.apiChecksumMismatch } diff --git a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/viaduct.swift b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/viaduct.swift index 0787cdbc0d779..cdc799cd098d8 100644 --- a/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/viaduct.swift +++ b/MozillaRustComponents/Sources/FocusRustComponentsWrapper/Generated/viaduct.swift @@ -739,6 +739,10 @@ public struct ClientSettings: Equatable, Hashable { * Maximum amount of redirects to follow (0 means redirects are not allowed) */ public var redirectLimit: UInt32 + /** + * OHTTP channel to use for all requests (if any) + */ + public var ohttpChannel: String? /** * Client default user-agent. * @@ -756,6 +760,9 @@ public struct ClientSettings: Equatable, Hashable { /** * Maximum amount of redirects to follow (0 means redirects are not allowed) */redirectLimit: UInt32 = UInt32(10), + /** + * OHTTP channel to use for all requests (if any) + */ohttpChannel: String?, /** * Client default user-agent. * @@ -764,6 +771,7 @@ public struct ClientSettings: Equatable, Hashable { */userAgent: String? = nil) { self.timeout = timeout self.redirectLimit = redirectLimit + self.ohttpChannel = ohttpChannel self.userAgent = userAgent } @@ -785,6 +793,7 @@ public struct FfiConverterTypeClientSettings: FfiConverterRustBuffer { try ClientSettings( timeout: FfiConverterUInt32.read(from: &buf), redirectLimit: FfiConverterUInt32.read(from: &buf), + ohttpChannel: FfiConverterOptionString.read(from: &buf), userAgent: FfiConverterOptionString.read(from: &buf) ) } @@ -792,6 +801,7 @@ public struct FfiConverterTypeClientSettings: FfiConverterRustBuffer { public static func write(_ value: ClientSettings, into buf: inout [UInt8]) { FfiConverterUInt32.write(value.timeout, into: &buf) FfiConverterUInt32.write(value.redirectLimit, into: &buf) + FfiConverterOptionString.write(value.ohttpChannel, into: &buf) FfiConverterOptionString.write(value.userAgent, into: &buf) } } @@ -812,6 +822,75 @@ public func FfiConverterTypeClientSettings_lower(_ value: ClientSettings) -> Rus } +/** + * Configuration for an OHTTP channel + */ +public struct OhttpConfig: Equatable, Hashable { + /** + * The relay URL that will proxy requests + */ + public var relayUrl: String + /** + * The gateway host that provides encryption keys and decrypts requests + */ + public var gatewayHost: String + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The relay URL that will proxy requests + */relayUrl: String, + /** + * The gateway host that provides encryption keys and decrypts requests + */gatewayHost: String) { + self.relayUrl = relayUrl + self.gatewayHost = gatewayHost + } + + + + +} + +#if compiler(>=6) +extension OhttpConfig: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeOhttpConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> OhttpConfig { + return + try OhttpConfig( + relayUrl: FfiConverterString.read(from: &buf), + gatewayHost: FfiConverterString.read(from: &buf) + ) + } + + public static func write(_ value: OhttpConfig, into buf: inout [UInt8]) { + FfiConverterString.write(value.relayUrl, into: &buf) + FfiConverterString.write(value.gatewayHost, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeOhttpConfig_lift(_ buf: RustBuffer) throws -> OhttpConfig { + return try FfiConverterTypeOhttpConfig.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeOhttpConfig_lower(_ value: OhttpConfig) -> RustBuffer { + return FfiConverterTypeOhttpConfig.lower(value) +} + + public struct Request: Equatable, Hashable { public var method: Method public var url: ViaductUrl @@ -1319,6 +1398,31 @@ fileprivate struct FfiConverterOptionData: FfiConverterRustBuffer { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { + typealias SwiftType = [String] + + public static func write(_ value: [String], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterString.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String] { + let len: Int32 = try readInt(&buf) + var seq = [String]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterString.read(from: &buf)) + } + return seq + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -1570,12 +1674,88 @@ private func uniffiForeignFutureDroppedCallback(handle: UInt64) { public func uniffiForeignFutureHandleCountViaduct() -> Int { UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count } +/** + * Send a request through an OHTTP channel. + * + * This encrypts the request and routes it through the configured OHTTP + * relay/gateway for the specified channel. + * + * # Arguments + * * `request` - The request to send + * * `channel` - The name of the OHTTP channel to use (e.g., "merino") + * + * # Example (Kotlin) + * ```kotlin + * val response = sendOhttpRequest( + * Request( + * method = Method.GET, + * url = "https://example.com/api", + * headers = mapOf("Accept" to "application/json"), + * body = null + * ), + * "merino" + * ) + * ``` + */ +public func sendOhttpRequest(request: Request, channel: String)async throws -> Response { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_viaduct_fn_func_send_ohttp_request(FfiConverterTypeRequest_lower(request),FfiConverterString.lower(channel) + ) + }, + pollFunc: ffi_viaduct_rust_future_poll_rust_buffer, + completeFunc: ffi_viaduct_rust_future_complete_rust_buffer, + freeFunc: ffi_viaduct_rust_future_free_rust_buffer, + liftFunc: FfiConverterTypeResponse_lift, + errorHandler: FfiConverterTypeViaductError_lift + ) +} public func initBackend(backend: Backend)throws {try rustCallWithError(FfiConverterTypeViaductError_lift) { uniffi_viaduct_fn_func_init_backend( FfiConverterTypeBackend_lower(backend),$0 ) } } +/** + * Clear all OHTTP channel configurations + */ +public func clearOhttpChannels() {try! rustCall() { + uniffi_viaduct_fn_func_clear_ohttp_channels($0 + ) +} +} +/** + * Configure default OHTTP channels for common Mozilla services + * This sets up: + * - "relay1": For general telemetry and services through Mozilla's shared gateway + * - "merino": For Firefox Suggest recommendations through Merino's dedicated relay/gateway + */ +public func configureDefaultOhttpChannels()throws {try rustCallWithError(FfiConverterTypeViaductError_lift) { + uniffi_viaduct_fn_func_configure_default_ohttp_channels($0 + ) +} +} +/** + * Configure an OHTTP channel with the given configuration + * If an existing OHTTP config exists with the same name, it will be overwritten + */ +public func configureOhttpChannel(channel: String, config: OhttpConfig)throws {try rustCallWithError(FfiConverterTypeViaductError_lift) { + uniffi_viaduct_fn_func_configure_ohttp_channel( + FfiConverterString.lower(channel), + FfiConverterTypeOhttpConfig_lower(config),$0 + ) +} +} +/** + * List all configured OHTTP channels + */ +public func listOhttpChannels() -> [String] { + return try! FfiConverterSequenceString.lift(try! rustCall() { + uniffi_viaduct_fn_func_list_ohttp_channels($0 + ) +}) +} /** * Allow non-HTTPS requests to the emulator loopback URL */ @@ -1612,9 +1792,24 @@ private let initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } + if (uniffi_viaduct_checksum_func_send_ohttp_request() != 6311) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_viaduct_checksum_func_init_backend() != 54406) { return InitializationResult.apiChecksumMismatch } + if (uniffi_viaduct_checksum_func_clear_ohttp_channels() != 2859) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_viaduct_checksum_func_configure_default_ohttp_channels() != 1921) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_viaduct_checksum_func_configure_ohttp_channel() != 19406) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_viaduct_checksum_func_list_ohttp_channels() != 38695) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_viaduct_checksum_func_allow_android_emulator_loopback() != 12993) { return InitializationResult.apiChecksumMismatch } diff --git a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/Metrics/Metrics.swift b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/Metrics/Metrics.swift index 1b1ecb57e38d9..bc44b0173d526 100644 --- a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/Metrics/Metrics.swift +++ b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/Metrics/Metrics.swift @@ -1,6 +1,6 @@ // -*- mode: Swift -*- -// AUTOGENERATED BY glean_parser v18.2.0. DO NOT EDIT. DO NOT COMMIT. +// AUTOGENERATED BY glean_parser v19.0.0. DO NOT EDIT. DO NOT COMMIT. #if canImport(Foundation) import Foundation @@ -23,7 +23,7 @@ extension GleanMetrics { // Intentionally left private, no external user can instantiate a new global object. } - public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2026, month: 4, day: 14, hour: 5, minute: 32, second: 29)) + public static let info = BuildInfo(buildDate: DateComponents(calendar: Calendar.current, timeZone: TimeZone(abbreviation: "UTC"), year: 2026, month: 4, day: 22, hour: 5, minute: 35, second: 55)) } enum AdsClient { diff --git a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/ads_client.swift b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/ads_client.swift index 7d53b119c390c..93df46f7b4126 100644 --- a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/ads_client.swift +++ b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/ads_client.swift @@ -470,6 +470,30 @@ fileprivate struct FfiConverterDouble: FfiConverterPrimitive { } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -518,11 +542,11 @@ public protocol MozAdsClientProtocol: AnyObject, Sendable { func clearCache() throws - func recordClick(clickUrl: String) throws + func recordClick(clickUrl: String, options: MozAdsCallbackOptions?) throws - func recordImpression(impressionUrl: String) throws + func recordImpression(impressionUrl: String, options: MozAdsCallbackOptions?) throws - func reportAd(reportUrl: String, reason: MozAdsReportReason) throws + func reportAd(reportUrl: String, reason: MozAdsReportReason, options: MozAdsCallbackOptions?) throws func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?) throws -> [String: MozAdsImage] @@ -591,32 +615,35 @@ open func clearCache()throws {try rustCallWithError(FfiConverterTypeMozAdsClie } } -open func recordClick(clickUrl: String)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func recordClick(clickUrl: String, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_record_click( self.uniffiCloneHandle(), - FfiConverterString.lower(clickUrl),$0 + FfiConverterString.lower(clickUrl), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func recordImpression(impressionUrl: String)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func recordImpression(impressionUrl: String, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_record_impression( self.uniffiCloneHandle(), - FfiConverterString.lower(impressionUrl),$0 + FfiConverterString.lower(impressionUrl), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func reportAd(reportUrl: String, reason: MozAdsReportReason)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { +open func reportAd(reportUrl: String, reason: MozAdsReportReason, options: MozAdsCallbackOptions? = nil)throws {try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_report_ad( self.uniffiCloneHandle(), FfiConverterString.lower(reportUrl), - FfiConverterTypeMozAdsReportReason_lower(reason),$0 + FfiConverterTypeMozAdsReportReason_lower(reason), + FfiConverterOptionTypeMozAdsCallbackOptions.lower(options),$0 ) } } -open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?)throws -> [String: MozAdsImage] { +open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions? = nil)throws -> [String: MozAdsImage] { return try FfiConverterDictionaryStringTypeMozAdsImage.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_image_ads( self.uniffiCloneHandle(), @@ -626,7 +653,7 @@ open func requestImageAds(mozAdRequests: [MozAdsPlacementRequest], options: MozA }) } -open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], options: MozAdsRequestOptions?)throws -> [String: [MozAdsSpoc]] { +open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], options: MozAdsRequestOptions? = nil)throws -> [String: [MozAdsSpoc]] { return try FfiConverterDictionaryStringSequenceTypeMozAdsSpoc.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_spoc_ads( self.uniffiCloneHandle(), @@ -636,7 +663,7 @@ open func requestSpocAds(mozAdRequests: [MozAdsPlacementRequestWithCount], optio }) } -open func requestTileAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions?)throws -> [String: MozAdsTile] { +open func requestTileAds(mozAdRequests: [MozAdsPlacementRequest], options: MozAdsRequestOptions? = nil)throws -> [String: MozAdsTile] { return try FfiConverterDictionaryStringTypeMozAdsTile.lift(try rustCallWithError(FfiConverterTypeMozAdsClientApiError_lift) { uniffi_ads_client_fn_method_mozadsclient_request_tile_ads( self.uniffiCloneHandle(), @@ -1491,6 +1518,56 @@ public func FfiConverterTypeMozAdsCachePolicy_lower(_ value: MozAdsCachePolicy) } +public struct MozAdsCallbackOptions: Equatable, Hashable { + public var ohttp: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(ohttp: Bool = false) { + self.ohttp = ohttp + } + + + + +} + +#if compiler(>=6) +extension MozAdsCallbackOptions: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMozAdsCallbackOptions: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MozAdsCallbackOptions { + return + try MozAdsCallbackOptions( + ohttp: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: MozAdsCallbackOptions, into buf: inout [UInt8]) { + FfiConverterBool.write(value.ohttp, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMozAdsCallbackOptions_lift(_ buf: RustBuffer) throws -> MozAdsCallbackOptions { + return try FfiConverterTypeMozAdsCallbackOptions.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMozAdsCallbackOptions_lower(_ value: MozAdsCallbackOptions) -> RustBuffer { + return FfiConverterTypeMozAdsCallbackOptions.lower(value) +} + + public struct MozAdsCallbacks: Equatable, Hashable { public var click: AdsClientUrl public var impression: AdsClientUrl @@ -1841,11 +1918,13 @@ public func FfiConverterTypeMozAdsPlacementRequestWithCount_lower(_ value: MozAd public struct MozAdsRequestOptions: Equatable, Hashable { public var cachePolicy: MozAdsCachePolicy? + public var ohttp: Bool // Default memberwise initializers are never public by default, so we // declare one manually. - public init(cachePolicy: MozAdsCachePolicy?) { + public init(cachePolicy: MozAdsCachePolicy?, ohttp: Bool = false) { self.cachePolicy = cachePolicy + self.ohttp = ohttp } @@ -1864,12 +1943,14 @@ public struct FfiConverterTypeMozAdsRequestOptions: FfiConverterRustBuffer { public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MozAdsRequestOptions { return try MozAdsRequestOptions( - cachePolicy: FfiConverterOptionTypeMozAdsCachePolicy.read(from: &buf) + cachePolicy: FfiConverterOptionTypeMozAdsCachePolicy.read(from: &buf), + ohttp: FfiConverterBool.read(from: &buf) ) } public static func write(_ value: MozAdsRequestOptions, into buf: inout [UInt8]) { FfiConverterOptionTypeMozAdsCachePolicy.write(value.cachePolicy, into: &buf) + FfiConverterBool.write(value.ohttp, into: &buf) } } @@ -2606,6 +2687,30 @@ fileprivate struct FfiConverterOptionTypeMozAdsCachePolicy: FfiConverterRustBuff } } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionTypeMozAdsCallbackOptions: FfiConverterRustBuffer { + typealias SwiftType = MozAdsCallbackOptions? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeMozAdsCallbackOptions.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeMozAdsCallbackOptions.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -2941,25 +3046,25 @@ private let initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_clear_cache() != 63953) { + if (uniffi_ads_client_checksum_method_mozadsclient_clear_cache() != 10112) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_record_click() != 2) { + if (uniffi_ads_client_checksum_method_mozadsclient_record_click() != 59910) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_record_impression() != 43275) { + if (uniffi_ads_client_checksum_method_mozadsclient_record_impression() != 57294) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_report_ad() != 18252) { + if (uniffi_ads_client_checksum_method_mozadsclient_report_ad() != 56767) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_image_ads() != 2157) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_image_ads() != 20861) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_spoc_ads() != 37780) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_spoc_ads() != 2130) { return InitializationResult.apiChecksumMismatch } - if (uniffi_ads_client_checksum_method_mozadsclient_request_tile_ads() != 26296) { + if (uniffi_ads_client_checksum_method_mozadsclient_request_tile_ads() != 10) { return InitializationResult.apiChecksumMismatch } if (uniffi_ads_client_checksum_method_mozadsclientbuilder_build() != 36609) { diff --git a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/fxa_client.swift b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/fxa_client.swift index d5f8881ca54ed..f86da27959763 100644 --- a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/fxa_client.swift +++ b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/fxa_client.swift @@ -3757,6 +3757,14 @@ public enum FxaError: Swift.Error, Equatable, Hashable, Foundation.LocalizedErro */ case Authentication(message: String) + /** + * Thrown when an authenticated account isn't allowed to perform some operation. Unlike + * `Authentication`, there's no problem with the account status. In some cases it + * might be possible to request additional scopes, and once granted, the operation + * may succeed. + */ + case Forbidden(message: String) + /** * Thrown if an operation fails due to network access problems. * The application may retry at a later time once connectivity is restored. @@ -3838,31 +3846,35 @@ public struct FfiConverterTypeFxaError: FfiConverterRustBuffer { message: try FfiConverterString.read(from: &buf) ) - case 2: return .Network( + case 2: return .Forbidden( + message: try FfiConverterString.read(from: &buf) + ) + + case 3: return .Network( message: try FfiConverterString.read(from: &buf) ) - case 3: return .NoExistingAuthFlow( + case 4: return .NoExistingAuthFlow( message: try FfiConverterString.read(from: &buf) ) - case 4: return .WrongAuthFlow( + case 5: return .WrongAuthFlow( message: try FfiConverterString.read(from: &buf) ) - case 5: return .OriginMismatch( + case 6: return .OriginMismatch( message: try FfiConverterString.read(from: &buf) ) - case 6: return .SyncScopedKeyMissingInServerResponse( + case 7: return .SyncScopedKeyMissingInServerResponse( message: try FfiConverterString.read(from: &buf) ) - case 7: return .Panic( + case 8: return .Panic( message: try FfiConverterString.read(from: &buf) ) - case 8: return .Other( + case 9: return .Other( message: try FfiConverterString.read(from: &buf) ) @@ -3879,20 +3891,22 @@ public struct FfiConverterTypeFxaError: FfiConverterRustBuffer { case .Authentication(_ /* message is ignored*/): writeInt(&buf, Int32(1)) - case .Network(_ /* message is ignored*/): + case .Forbidden(_ /* message is ignored*/): writeInt(&buf, Int32(2)) - case .NoExistingAuthFlow(_ /* message is ignored*/): + case .Network(_ /* message is ignored*/): writeInt(&buf, Int32(3)) - case .WrongAuthFlow(_ /* message is ignored*/): + case .NoExistingAuthFlow(_ /* message is ignored*/): writeInt(&buf, Int32(4)) - case .OriginMismatch(_ /* message is ignored*/): + case .WrongAuthFlow(_ /* message is ignored*/): writeInt(&buf, Int32(5)) - case .SyncScopedKeyMissingInServerResponse(_ /* message is ignored*/): + case .OriginMismatch(_ /* message is ignored*/): writeInt(&buf, Int32(6)) - case .Panic(_ /* message is ignored*/): + case .SyncScopedKeyMissingInServerResponse(_ /* message is ignored*/): writeInt(&buf, Int32(7)) - case .Other(_ /* message is ignored*/): + case .Panic(_ /* message is ignored*/): writeInt(&buf, Int32(8)) + case .Other(_ /* message is ignored*/): + writeInt(&buf, Int32(9)) } diff --git a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/merino.swift b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/merino.swift index 21ecfdbd12477..5787ce8ffc493 100644 --- a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/merino.swift +++ b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/merino.swift @@ -680,6 +680,159 @@ public func FfiConverterTypeCuratedRecommendationsClient_lower(_ value: CuratedR + + +/** + * A client for the merino suggest endpoint. + * + * Use [`SuggestClient::new`] to create an instance, then call + * [`SuggestClient::get_suggestions`] to fetch suggestions for a query. + */ +public protocol SuggestClientProtocol: AnyObject, Sendable { + + /** + * Fetches suggestions from the merino suggest endpoint for the given query. + * + * Returns the raw JSON response body as a string, or `None` if the server + * returned HTTP 204 (no suggestions available for weather). + */ + func getSuggestions(query: String, options: SuggestOptions) throws -> String? + +} +/** + * A client for the merino suggest endpoint. + * + * Use [`SuggestClient::new`] to create an instance, then call + * [`SuggestClient::get_suggestions`] to fetch suggestions for a query. + */ +open class SuggestClient: SuggestClientProtocol, @unchecked Sendable { + fileprivate let handle: UInt64 + + /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public struct NoHandle { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + required public init(unsafeFromHandle handle: UInt64) { + self.handle = handle + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public init(noHandle: NoHandle) { + self.handle = 0 + } + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { uniffi_merino_fn_clone_suggestclient(self.handle, $0) } + } + /** + * Creates a new `SuggestClient` from the given configuration. + */ +public convenience init(config: SuggestConfig)throws { + let handle = + try rustCallWithError(FfiConverterTypeMerinoSuggestApiError_lift) { + uniffi_merino_fn_constructor_suggestclient_new( + FfiConverterTypeSuggestConfig_lower(config),$0 + ) +} + self.init(unsafeFromHandle: handle) +} + + deinit { + if handle == 0 { + // Mock objects have handle=0 don't try to free them + return + } + + try! rustCall { uniffi_merino_fn_free_suggestclient(handle, $0) } + } + + + + + /** + * Fetches suggestions from the merino suggest endpoint for the given query. + * + * Returns the raw JSON response body as a string, or `None` if the server + * returned HTTP 204 (no suggestions available for weather). + */ +open func getSuggestions(query: String, options: SuggestOptions)throws -> String? { + return try FfiConverterOptionString.lift(try rustCallWithError(FfiConverterTypeMerinoSuggestApiError_lift) { + uniffi_merino_fn_method_suggestclient_get_suggestions( + self.uniffiCloneHandle(), + FfiConverterString.lower(query), + FfiConverterTypeSuggestOptions_lower(options),$0 + ) +}) +} + + + +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSuggestClient: FfiConverter { + typealias FfiType = UInt64 + typealias SwiftType = SuggestClient + + public static func lift(_ handle: UInt64) throws -> SuggestClient { + return SuggestClient(unsafeFromHandle: handle) + } + + public static func lower(_ value: SuggestClient) -> UInt64 { + return value.uniffiCloneHandle() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SuggestClient { + let handle: UInt64 = try readInt(&buf) + return try lift(handle) + } + + public static func write(_ value: SuggestClient, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestClient_lift(_ handle: UInt64) throws -> SuggestClient { + return try FfiConverterTypeSuggestClient.lift(handle) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestClient_lower(_ value: SuggestClient) -> UInt64 { + return FfiConverterTypeSuggestClient.lower(value) +} + + + + /** * Configuration options for initializing a [`CuratedRecommendationsClient`](crate::curated_recommendations::CuratedRecommendationsClient). */ @@ -1642,6 +1795,199 @@ public func FfiConverterTypeSectionSettings_lower(_ value: SectionSettings) -> R } +/** + * Configuration for the merino suggest client. + */ +public struct SuggestConfig: Equatable, Hashable, Codable { + /** + * The base host for the merino endpoint. Defaults to the production host if not set. + */ + public var baseHost: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * The base host for the merino endpoint. Defaults to the production host if not set. + */baseHost: String?) { + self.baseHost = baseHost + } + + + + +} + +#if compiler(>=6) +extension SuggestConfig: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSuggestConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SuggestConfig { + return + try SuggestConfig( + baseHost: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: SuggestConfig, into buf: inout [UInt8]) { + FfiConverterOptionString.write(value.baseHost, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestConfig_lift(_ buf: RustBuffer) throws -> SuggestConfig { + return try FfiConverterTypeSuggestConfig.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestConfig_lower(_ value: SuggestConfig) -> RustBuffer { + return FfiConverterTypeSuggestConfig.lower(value) +} + + +/** + * Options for a suggest request, mapped to merino suggest endpoint query parameters. + * All fields are optional — omitted fields are not sent to merino. + */ +public struct SuggestOptions: Equatable, Hashable, Codable { + /** + * List of suggestion providers to query (e.g. `["wikipedia", "adm"]`). + */ + public var providers: [String]? + /** + * Identifier of which part of firefox the request comes from (e.g. `"urlbar"`, `"newtab"`). + */ + public var source: String? + /** + * ISO 3166-1 country code (e.g. `"US"`). + */ + public var country: String? + /** + * Comma separated string of subdivision code(s) (e.g. `"CA"`). + */ + public var region: String? + /** + * City name (e.g. `"San Francisco"`). + */ + public var city: String? + /** + * List of any experiments or rollouts that are affecting the client's Suggest experience. + * If Merino recognizes any of them it will modify its behavior accordingly. + */ + public var clientVariants: [String]? + /** + * For AccuWeather provider, the request type should be either a "location" or "weather" string. For "location" it will get location completion suggestion. For "weather" it will return weather suggestions. + * If omitted, it defaults to weather suggestions. + */ + public var requestType: String? + /** + * The `Accept-Language` header value to forward to Merino (e.g. `"en-US"`). + */ + public var acceptLanguage: String? + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + /** + * List of suggestion providers to query (e.g. `["wikipedia", "adm"]`). + */providers: [String]?, + /** + * Identifier of which part of firefox the request comes from (e.g. `"urlbar"`, `"newtab"`). + */source: String?, + /** + * ISO 3166-1 country code (e.g. `"US"`). + */country: String?, + /** + * Comma separated string of subdivision code(s) (e.g. `"CA"`). + */region: String?, + /** + * City name (e.g. `"San Francisco"`). + */city: String?, + /** + * List of any experiments or rollouts that are affecting the client's Suggest experience. + * If Merino recognizes any of them it will modify its behavior accordingly. + */clientVariants: [String]?, + /** + * For AccuWeather provider, the request type should be either a "location" or "weather" string. For "location" it will get location completion suggestion. For "weather" it will return weather suggestions. + * If omitted, it defaults to weather suggestions. + */requestType: String?, + /** + * The `Accept-Language` header value to forward to Merino (e.g. `"en-US"`). + */acceptLanguage: String?) { + self.providers = providers + self.source = source + self.country = country + self.region = region + self.city = city + self.clientVariants = clientVariants + self.requestType = requestType + self.acceptLanguage = acceptLanguage + } + + + + +} + +#if compiler(>=6) +extension SuggestOptions: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeSuggestOptions: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SuggestOptions { + return + try SuggestOptions( + providers: FfiConverterOptionSequenceString.read(from: &buf), + source: FfiConverterOptionString.read(from: &buf), + country: FfiConverterOptionString.read(from: &buf), + region: FfiConverterOptionString.read(from: &buf), + city: FfiConverterOptionString.read(from: &buf), + clientVariants: FfiConverterOptionSequenceString.read(from: &buf), + requestType: FfiConverterOptionString.read(from: &buf), + acceptLanguage: FfiConverterOptionString.read(from: &buf) + ) + } + + public static func write(_ value: SuggestOptions, into buf: inout [UInt8]) { + FfiConverterOptionSequenceString.write(value.providers, into: &buf) + FfiConverterOptionString.write(value.source, into: &buf) + FfiConverterOptionString.write(value.country, into: &buf) + FfiConverterOptionString.write(value.region, into: &buf) + FfiConverterOptionString.write(value.city, into: &buf) + FfiConverterOptionSequenceString.write(value.clientVariants, into: &buf) + FfiConverterOptionString.write(value.requestType, into: &buf) + FfiConverterOptionString.write(value.acceptLanguage, into: &buf) + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestOptions_lift(_ buf: RustBuffer) throws -> SuggestOptions { + return try FfiConverterTypeSuggestOptions.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeSuggestOptions_lower(_ value: SuggestOptions) -> RustBuffer { + return FfiConverterTypeSuggestOptions.lower(value) +} + + /** * Properties for a single tile within a responsive layout. */ @@ -1985,6 +2331,98 @@ public func FfiConverterTypeCuratedRecommendationsApiError_lower(_ value: Curate return FfiConverterTypeCuratedRecommendationsApiError.lower(value) } + +public enum MerinoSuggestApiError: Swift.Error, Equatable, Hashable, Codable, Foundation.LocalizedError { + + + + /** + * A network-level failure. + */ + case Network(reason: String + ) + /** + * Any other error, e.g. HTTP errors, validation errors. + */ + case Other(code: UInt16?, reason: String + ) + + + + + + + public var errorDescription: String? { + String(reflecting: self) + } + +} + +#if compiler(>=6) +extension MerinoSuggestApiError: Sendable {} +#endif + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public struct FfiConverterTypeMerinoSuggestApiError: FfiConverterRustBuffer { + typealias SwiftType = MerinoSuggestApiError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MerinoSuggestApiError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Network( + reason: try FfiConverterString.read(from: &buf) + ) + case 2: return .Other( + code: try FfiConverterOptionUInt16.read(from: &buf), + reason: try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: MerinoSuggestApiError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Network(reason): + writeInt(&buf, Int32(1)) + FfiConverterString.write(reason, into: &buf) + + + case let .Other(code,reason): + writeInt(&buf, Int32(2)) + FfiConverterOptionUInt16.write(code, into: &buf) + FfiConverterString.write(reason, into: &buf) + + } + } +} + + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMerinoSuggestApiError_lift(_ buf: RustBuffer) throws -> MerinoSuggestApiError { + return try FfiConverterTypeMerinoSuggestApiError.lift(buf) +} + +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +public func FfiConverterTypeMerinoSuggestApiError_lower(_ value: MerinoSuggestApiError) -> RustBuffer { + return FfiConverterTypeMerinoSuggestApiError.lower(value) +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -2422,9 +2860,15 @@ private let initializationResult: InitializationResult = { if (uniffi_merino_checksum_method_curatedrecommendationsclient_get_curated_recommendations() != 52246) { return InitializationResult.apiChecksumMismatch } + if (uniffi_merino_checksum_method_suggestclient_get_suggestions() != 55159) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_merino_checksum_constructor_curatedrecommendationsclient_new() != 18166) { return InitializationResult.apiChecksumMismatch } + if (uniffi_merino_checksum_constructor_suggestclient_new() != 14568) { + return InitializationResult.apiChecksumMismatch + } return InitializationResult.ok }() diff --git a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/remote_settings.swift b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/remote_settings.swift index a22fe8dd706c0..ab7d0083a1675 100644 --- a/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/remote_settings.swift +++ b/MozillaRustComponents/Sources/MozillaRustComponentsWrapper/Generated/remote_settings.swift @@ -518,175 +518,6 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer { -public protocol RemoteSettingsProtocol: AnyObject, Sendable { - - /** - * Download an attachment with the provided id to the provided path. - */ - func downloadAttachmentToPath(attachmentId: String, path: String) throws - - /** - * Fetch all records for the configuration this client was initialized with. - */ - func getRecords() throws -> RemoteSettingsResponse - - /** - * Fetch all records added to the server since the provided timestamp, - * using the configuration this client was initialized with. - */ - func getRecordsSince(timestamp: UInt64) throws -> RemoteSettingsResponse - -} -open class RemoteSettings: RemoteSettingsProtocol, @unchecked Sendable { - fileprivate let handle: UInt64 - - /// Used to instantiate a [FFIObject] without an actual handle, for fakes in tests, mostly. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public struct NoHandle { - public init() {} - } - - // TODO: We'd like this to be `private` but for Swifty reasons, - // we can't implement `FfiConverter` without making this `required` and we can't - // make it `required` without making it `public`. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - required public init(unsafeFromHandle handle: UInt64) { - self.handle = handle - } - - // This constructor can be used to instantiate a fake object. - // - Parameter noHandle: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. - // - // - Warning: - // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing handle the FFI lower functions will crash. -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public init(noHandle: NoHandle) { - self.handle = 0 - } - -#if swift(>=5.8) - @_documentation(visibility: private) -#endif - public func uniffiCloneHandle() -> UInt64 { - return try! rustCall { uniffi_remote_settings_fn_clone_remotesettings(self.handle, $0) } - } - /** - * Construct a new Remote Settings client with the given configuration. - */ -public convenience init(remoteSettingsConfig: RemoteSettingsConfig)throws { - let handle = - try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_constructor_remotesettings_new( - FfiConverterTypeRemoteSettingsConfig_lower(remoteSettingsConfig),$0 - ) -} - self.init(unsafeFromHandle: handle) -} - - deinit { - if handle == 0 { - // Mock objects have handle=0 don't try to free them - return - } - - try! rustCall { uniffi_remote_settings_fn_free_remotesettings(handle, $0) } - } - - - - - /** - * Download an attachment with the provided id to the provided path. - */ -open func downloadAttachmentToPath(attachmentId: String, path: String)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_download_attachment_to_path( - self.uniffiCloneHandle(), - FfiConverterString.lower(attachmentId), - FfiConverterString.lower(path),$0 - ) -} -} - - /** - * Fetch all records for the configuration this client was initialized with. - */ -open func getRecords()throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse_lift(try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_get_records( - self.uniffiCloneHandle(),$0 - ) -}) -} - - /** - * Fetch all records added to the server since the provided timestamp, - * using the configuration this client was initialized with. - */ -open func getRecordsSince(timestamp: UInt64)throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse_lift(try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { - uniffi_remote_settings_fn_method_remotesettings_get_records_since( - self.uniffiCloneHandle(), - FfiConverterUInt64.lower(timestamp),$0 - ) -}) -} - - - -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettings: FfiConverter { - typealias FfiType = UInt64 - typealias SwiftType = RemoteSettings - - public static func lift(_ handle: UInt64) throws -> RemoteSettings { - return RemoteSettings(unsafeFromHandle: handle) - } - - public static func lower(_ value: RemoteSettings) -> UInt64 { - return value.uniffiCloneHandle() - } - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettings { - let handle: UInt64 = try readInt(&buf) - return try lift(handle) - } - - public static func write(_ value: RemoteSettings, into buf: inout [UInt8]) { - writeInt(&buf, lower(value)) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettings_lift(_ handle: UInt64) throws -> RemoteSettings { - return try FfiConverterTypeRemoteSettings.lift(handle) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettings_lower(_ value: RemoteSettings) -> UInt64 { - return FfiConverterTypeRemoteSettings.lower(value) -} - - - - - - /** * Client for a single Remote Settings collection * @@ -712,6 +543,11 @@ public protocol RemoteSettingsClientProtocol: AnyObject, Sendable { */ func getAttachment(record: RemoteSettingsRecord) throws -> Data + /** + * Returns the last_modified value for the collection as an unsigned int64. + */ + func getLastModifiedTimestamp() -> UInt64? + /** * Get the current set of records. * @@ -837,6 +673,17 @@ open func getAttachment(record: RemoteSettingsRecord)throws -> Data { FfiConverterTypeRemoteSettingsRecord_lower(record),$0 ) }) +} + + /** + * Returns the last_modified value for the collection as an unsigned int64. + */ +open func getLastModifiedTimestamp() -> UInt64? { + return try! FfiConverterOptionUInt64.lift(try! rustCall() { + uniffi_remote_settings_fn_method_remotesettingsclient_get_last_modified_timestamp( + self.uniffiCloneHandle(),$0 + ) +}) } /** @@ -989,7 +836,7 @@ public protocol RemoteSettingsServiceProtocol: AnyObject, Sendable { * Only intended for QA/debugging. Swapping the remote settings server in the middle of * execution can cause weird effects. */ - func updateConfig(config: RemoteSettingsConfig2) throws + func updateConfig(config: RemoteSettingsConfig) throws } /** @@ -1049,12 +896,12 @@ open class RemoteSettingsService: RemoteSettingsServiceProtocol, @unchecked Send * directory does not exist, it will be created when the storage is first used. Only the * directory and the SQLite files will be created, any parent directories must already exist. */ -public convenience init(storageDir: String, config: RemoteSettingsConfig2) { +public convenience init(storageDir: String, config: RemoteSettingsConfig) { let handle = try! rustCall() { uniffi_remote_settings_fn_constructor_remotesettingsservice_new( FfiConverterString.lower(storageDir), - FfiConverterTypeRemoteSettingsConfig2_lower(config),$0 + FfiConverterTypeRemoteSettingsConfig_lower(config),$0 ) } self.init(unsafeFromHandle: handle) @@ -1117,10 +964,10 @@ open func sync()throws -> [String] { * Only intended for QA/debugging. Swapping the remote settings server in the middle of * execution can cause weird effects. */ -open func updateConfig(config: RemoteSettingsConfig2)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { +open func updateConfig(config: RemoteSettingsConfig)throws {try rustCallWithError(FfiConverterTypeRemoteSettingsError_lift) { uniffi_remote_settings_fn_method_remotesettingsservice_update_config( self.uniffiCloneHandle(), - FfiConverterTypeRemoteSettingsConfig2_lower(config),$0 + FfiConverterTypeRemoteSettingsConfig_lower(config),$0 ) } } @@ -1243,84 +1090,10 @@ public func FfiConverterTypeAttachment_lower(_ value: Attachment) -> RustBuffer } -/** - * Custom configuration for the client. - * Currently includes the following: - * - `server`: The Remote Settings server to use. If not specified, defaults to the production server (`RemoteSettingsServer::Prod`). - * - `server_url`: An optional custom Remote Settings server URL. Deprecated; please use `server` instead. - * - `bucket_name`: The optional name of the bucket containing the collection on the server. If not specified, the standard bucket will be used. - * - `collection_name`: The name of the collection for the settings server. - */ -public struct RemoteSettingsConfig: Equatable, Hashable { - public var collectionName: String - public var bucketName: String? - public var serverUrl: String? - public var server: RemoteSettingsServer? - - // Default memberwise initializers are never public by default, so we - // declare one manually. - public init(collectionName: String, bucketName: String? = nil, serverUrl: String? = nil, server: RemoteSettingsServer? = nil) { - self.collectionName = collectionName - self.bucketName = bucketName - self.serverUrl = serverUrl - self.server = server - } - - - - -} - -#if compiler(>=6) -extension RemoteSettingsConfig: Sendable {} -#endif - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettingsConfig: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig { - return - try RemoteSettingsConfig( - collectionName: FfiConverterString.read(from: &buf), - bucketName: FfiConverterOptionString.read(from: &buf), - serverUrl: FfiConverterOptionString.read(from: &buf), - server: FfiConverterOptionTypeRemoteSettingsServer.read(from: &buf) - ) - } - - public static func write(_ value: RemoteSettingsConfig, into buf: inout [UInt8]) { - FfiConverterString.write(value.collectionName, into: &buf) - FfiConverterOptionString.write(value.bucketName, into: &buf) - FfiConverterOptionString.write(value.serverUrl, into: &buf) - FfiConverterOptionTypeRemoteSettingsServer.write(value.server, into: &buf) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsConfig_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig { - return try FfiConverterTypeRemoteSettingsConfig.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsConfig_lower(_ value: RemoteSettingsConfig) -> RustBuffer { - return FfiConverterTypeRemoteSettingsConfig.lower(value) -} - - /** * Remote settings configuration - * - * This is the version used in the new API, hence the `2` at the end. The plan is to move - * consumers to the new API, remove the RemoteSettingsConfig struct, then remove the `2` from this - * name. */ -public struct RemoteSettingsConfig2: Equatable, Hashable { +public struct RemoteSettingsConfig: Equatable, Hashable { /** * The Remote Settings server to use. Defaults to [RemoteSettingsServer::Prod], */ @@ -1357,23 +1130,23 @@ public struct RemoteSettingsConfig2: Equatable, Hashable { } #if compiler(>=6) -extension RemoteSettingsConfig2: Sendable {} +extension RemoteSettingsConfig: Sendable {} #endif #if swift(>=5.8) @_documentation(visibility: private) #endif -public struct FfiConverterTypeRemoteSettingsConfig2: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig2 { +public struct FfiConverterTypeRemoteSettingsConfig: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsConfig { return - try RemoteSettingsConfig2( + try RemoteSettingsConfig( server: FfiConverterOptionTypeRemoteSettingsServer.read(from: &buf), bucketName: FfiConverterOptionString.read(from: &buf), appContext: FfiConverterOptionTypeRemoteSettingsContext.read(from: &buf) ) } - public static func write(_ value: RemoteSettingsConfig2, into buf: inout [UInt8]) { + public static func write(_ value: RemoteSettingsConfig, into buf: inout [UInt8]) { FfiConverterOptionTypeRemoteSettingsServer.write(value.server, into: &buf) FfiConverterOptionString.write(value.bucketName, into: &buf) FfiConverterOptionTypeRemoteSettingsContext.write(value.appContext, into: &buf) @@ -1384,15 +1157,15 @@ public struct FfiConverterTypeRemoteSettingsConfig2: FfiConverterRustBuffer { #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeRemoteSettingsConfig2_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig2 { - return try FfiConverterTypeRemoteSettingsConfig2.lift(buf) +public func FfiConverterTypeRemoteSettingsConfig_lift(_ buf: RustBuffer) throws -> RemoteSettingsConfig { + return try FfiConverterTypeRemoteSettingsConfig.lift(buf) } #if swift(>=5.8) @_documentation(visibility: private) #endif -public func FfiConverterTypeRemoteSettingsConfig2_lower(_ value: RemoteSettingsConfig2) -> RustBuffer { - return FfiConverterTypeRemoteSettingsConfig2.lower(value) +public func FfiConverterTypeRemoteSettingsConfig_lower(_ value: RemoteSettingsConfig) -> RustBuffer { + return FfiConverterTypeRemoteSettingsConfig.lower(value) } @@ -1636,64 +1409,6 @@ public func FfiConverterTypeRemoteSettingsRecord_lower(_ value: RemoteSettingsRe } -/** - * Data structure representing the top-level response from the Remote Settings. - * [last_modified] will be extracted from the etag header of the response. - */ -public struct RemoteSettingsResponse: Equatable, Hashable { - public var records: [RemoteSettingsRecord] - public var lastModified: UInt64 - - // Default memberwise initializers are never public by default, so we - // declare one manually. - public init(records: [RemoteSettingsRecord], lastModified: UInt64) { - self.records = records - self.lastModified = lastModified - } - - - - -} - -#if compiler(>=6) -extension RemoteSettingsResponse: Sendable {} -#endif - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public struct FfiConverterTypeRemoteSettingsResponse: FfiConverterRustBuffer { - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RemoteSettingsResponse { - return - try RemoteSettingsResponse( - records: FfiConverterSequenceTypeRemoteSettingsRecord.read(from: &buf), - lastModified: FfiConverterUInt64.read(from: &buf) - ) - } - - public static func write(_ value: RemoteSettingsResponse, into buf: inout [UInt8]) { - FfiConverterSequenceTypeRemoteSettingsRecord.write(value.records, into: &buf) - FfiConverterUInt64.write(value.lastModified, into: &buf) - } -} - - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsResponse_lift(_ buf: RustBuffer) throws -> RemoteSettingsResponse { - return try FfiConverterTypeRemoteSettingsResponse.lift(buf) -} - -#if swift(>=5.8) -@_documentation(visibility: private) -#endif -public func FfiConverterTypeRemoteSettingsResponse_lower(_ value: RemoteSettingsResponse) -> RustBuffer { - return FfiConverterTypeRemoteSettingsResponse.lower(value) -} - - /** * Public error class, this is what we return to consumers */ @@ -1883,6 +1598,30 @@ public func FfiConverterTypeRemoteSettingsServer_lower(_ value: RemoteSettingsSe } +#if swift(>=5.8) +@_documentation(visibility: private) +#endif +fileprivate struct FfiConverterOptionUInt64: FfiConverterRustBuffer { + typealias SwiftType = UInt64? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterUInt64.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterUInt64.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + #if swift(>=5.8) @_documentation(visibility: private) #endif @@ -2212,21 +1951,15 @@ private let initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_remote_settings_checksum_method_remotesettings_download_attachment_to_path() != 64720) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_method_remotesettings_get_records() != 36167) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_method_remotesettings_get_records_since() != 45558) { - return InitializationResult.apiChecksumMismatch - } if (uniffi_remote_settings_checksum_method_remotesettingsclient_collection_name() != 54184) { return InitializationResult.apiChecksumMismatch } if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_attachment() != 10695) { return InitializationResult.apiChecksumMismatch } + if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_last_modified_timestamp() != 46461) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_remote_settings_checksum_method_remotesettingsclient_get_records() != 52048) { return InitializationResult.apiChecksumMismatch } @@ -2251,13 +1984,10 @@ private let initializationResult: InitializationResult = { if (uniffi_remote_settings_checksum_method_remotesettingsservice_sync() != 41684) { return InitializationResult.apiChecksumMismatch } - if (uniffi_remote_settings_checksum_method_remotesettingsservice_update_config() != 29011) { - return InitializationResult.apiChecksumMismatch - } - if (uniffi_remote_settings_checksum_constructor_remotesettings_new() != 20240) { + if (uniffi_remote_settings_checksum_method_remotesettingsservice_update_config() != 23848) { return InitializationResult.apiChecksumMismatch } - if (uniffi_remote_settings_checksum_constructor_remotesettingsservice_new() != 58353) { + if (uniffi_remote_settings_checksum_constructor_remotesettingsservice_new() != 24841) { return InitializationResult.apiChecksumMismatch } diff --git a/SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3e8c4e0791173..7eefd4d533b2b 100644 --- a/SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/onevcat/Kingfisher.git", "state" : { - "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", - "version" : "7.12.0" + "revision" : "c152c1915f60c51e4afa0752656993ee5b3c63db", + "version" : "8.8.1" } }, { diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 28a822fc96feb..a1954ab4df16c 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -526,6 +526,7 @@ 43162A2F2492DB7800F91658 /* EmptyPrivateTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43162A2E2492DB7800F91658 /* EmptyPrivateTabsView.swift */; }; 43175DB626B8774D00C41C31 /* Ads.js in Resources */ = {isa = PBXBuildFile; fileRef = 43175DB526B8774D00C41C31 /* Ads.js */; }; 43175DB826B87D2C00C41C31 /* AdsTelemetryHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43175DB726B87D2C00C41C31 /* AdsTelemetryHelper.swift */; }; + 431854E32F9A3978001BCE78 /* WorldCup.strings in Resources */ = {isa = PBXBuildFile; fileRef = 431854E12F9A3977001BCE78 /* WorldCup.strings */; }; 431C0CA925C890E500395CE4 /* DefaultBrowserOnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C0CA825C890E500395CE4 /* DefaultBrowserOnboardingViewModel.swift */; }; 431C0D1E25C9DC4D00395CE4 /* DefaultBrowserOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431C0D1D25C9DC4D00395CE4 /* DefaultBrowserOnboardingTests.swift */; }; 431F0C4A2AC1A112006D7D49 /* TabLocation.strings in Resources */ = {isa = PBXBuildFile; fileRef = 431F0C482AC1A112006D7D49 /* TabLocation.strings */; }; @@ -758,8 +759,6 @@ 612194E32CE507CF001664BB /* WallpaperBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E22CE507CF001664BB /* WallpaperBackgroundView.swift */; }; 612194E62CE50A93001664BB /* WallpaperAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E52CE50A93001664BB /* WallpaperAction.swift */; }; 612194E82CE50A9B001664BB /* WallpaperState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E72CE50A9B001664BB /* WallpaperState.swift */; }; - 613489A32D942A020009AF01 /* ToastTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613489A22D942A020009AF01 /* ToastTelemetry.swift */; }; - 613489A52D9443610009AF01 /* ToastTelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 613489A42D9443610009AF01 /* ToastTelemetryTests.swift */; }; 61497F3C2F687B5A00BC5ACA /* AIControlsSettingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61497F3B2F687B5A00BC5ACA /* AIControlsSettingTests.swift */; }; 61497F3E2F68856600BC5ACA /* MockGeneralSettingsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61497F3D2F68856000BC5ACA /* MockGeneralSettingsDelegate.swift */; }; 614985482F6B0B6500BC5ACA /* AIControlsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614985472F6B0B6200BC5ACA /* AIControlsModel.swift */; }; @@ -1538,8 +1537,8 @@ BDB4E7212F337AAB000DB45F /* LocationTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB4E7202F337AAB000DB45F /* LocationTextFieldTests.swift */; }; BDD262562CE3AC8200DF2C62 /* PrivacyWindowHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDD262552CE3AC8200DF2C62 /* PrivacyWindowHelper.swift */; }; BDE2DB332DB1024E002FAE26 /* TabWebViewPreviewAppearanceConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE2DB322DB1024E002FAE26 /* TabWebViewPreviewAppearanceConfiguration.swift */; }; - C209316E2F3CA52E00F0B05C /* VoiceSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C209316D2F3CA52E00F0B05C /* VoiceSearchCoordinator.swift */; }; - C20931722F3CAB0100F0B05C /* VoiceSearchCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20931712F3CAB0100F0B05C /* VoiceSearchCoordinatorTests.swift */; }; + C209316E2F3CA52E00F0B05C /* QuickAnswersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C209316D2F3CA52E00F0B05C /* QuickAnswersCoordinator.swift */; }; + C20931722F3CAB0100F0B05C /* QuickAnswersCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C20931712F3CAB0100F0B05C /* QuickAnswersCoordinatorTests.swift */; }; C2126EF82F4477EE001C01FE /* SummarizerLanguageExpansionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2126EF72F4477EE001C01FE /* SummarizerLanguageExpansionConfiguration.swift */; }; C2126EFA2F447A46001C01FE /* SummarizerNimbusUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2126EF92F447A46001C01FE /* SummarizerNimbusUtilsTests.swift */; }; C21326992F5713F000746393 /* SummarizerLanguageProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21326982F5713F000746393 /* SummarizerLanguageProviderTests.swift */; }; @@ -1623,6 +1622,7 @@ C2D71B972A384F40003DEC7A /* ThemedSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D71B962A384F40003DEC7A /* ThemedSubtitleTableViewCell.swift */; }; C2D71B992A384F6A003DEC7A /* ThemedLeftAlignedTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D71B982A384F6A003DEC7A /* ThemedLeftAlignedTableViewCell.swift */; }; C2D71B9B2A3850B4003DEC7A /* ThemedTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D71B9A2A3850B4003DEC7A /* ThemedTableViewCellViewModel.swift */; }; + C2D7AC3D2F9B5DC30090EC28 /* WorldCupAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D7AC3C2F9B5DC30090EC28 /* WorldCupAction.swift */; }; C2D80BE72AADE38100CDF7A9 /* CredentialAutofillCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D80BE62AADE38100CDF7A9 /* CredentialAutofillCoordinator.swift */; }; C2D80BEB2AAF395200CDF7A9 /* CredentialAutofillCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D80BEA2AAF395200CDF7A9 /* CredentialAutofillCoordinatorTests.swift */; }; C2D80BED2AAF3C6B00CDF7A9 /* MockBrowserCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2D80BEC2AAF3C6B00CDF7A9 /* MockBrowserCoordinator.swift */; }; @@ -1690,7 +1690,6 @@ C820439A2523DC4500740B71 /* libStorage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FCAE21A1ABB51F800877008 /* libStorage.a */; }; C82043AE2523DC8B00740B71 /* Sync.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2827315E1ABC9BE600AA1954 /* Sync.framework */; }; C82043C32523DD6A00740B71 /* Sync.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 2827315E1ABC9BE600AA1954 /* Sync.framework */; }; - C825E9832832A425006CB811 /* NimbusSearchBarLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C825E9822832A425006CB811 /* NimbusSearchBarLayer.swift */; }; C82A94F2269F68ED00624AA7 /* LegacyFeatureFlagsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82A94E6269CB77F00624AA7 /* LegacyFeatureFlagsManager.swift */; }; C82A94F3269F68F300624AA7 /* CoreFlaggableFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82A94E4269CB77500624AA7 /* CoreFlaggableFeature.swift */; }; C82CDD47233E8996002E2743 /* Tab+ChangeUserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82CDD45233E8996002E2743 /* Tab+ChangeUserAgent.swift */; }; @@ -4012,6 +4011,7 @@ 431853AA2A0911440099B0E0 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = "gl.lproj/Default Browser.strings"; sourceTree = ""; }; 431853AB2A0911440099B0E0 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Today.strings; sourceTree = ""; }; 431853AC2A0911440099B0E0 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/BookmarkPanelDeleteConfirm.strings; sourceTree = ""; }; + 431854E22F9A3978001BCE78 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/WorldCup.strings; sourceTree = ""; }; 431863C92CE2210900743CA3 /* is */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = is; path = is.lproj/LibraryPanel.strings; sourceTree = ""; }; 431875102DDB4831002A6501 /* es-CL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-CL"; path = "es-CL.lproj/Shopping.strings"; sourceTree = ""; }; 431889802CD8E1A100365FD8 /* ab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ab; path = ab.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -4235,6 +4235,7 @@ 4320BE4929D1B24B00D0B308 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Onboarding.strings; sourceTree = ""; }; 4320DD052D352B5E00FC5C5E /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/SocialShare.strings; sourceTree = ""; }; 4320E17E2A16E5A9009A4B5F /* CreditCardExtras.ios.mjs */ = {isa = PBXFileReference; lastKnownFileType = text; path = CreditCardExtras.ios.mjs; sourceTree = ""; }; + 4320E7B22F9A39EA00C305EA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/WorldCup.strings; sourceTree = ""; }; 432106DE2D47A07900D5FA08 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/FxANotification.strings; sourceTree = ""; }; 432106DF2D47A07900D5FA08 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/KeyboardAccessory.strings; sourceTree = ""; }; 43211DCD2C3C045A00E4CA4D /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/EditAddress.strings; sourceTree = ""; }; @@ -4421,6 +4422,7 @@ 4328FAD129D1B35100DABB9A /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Settings.strings; sourceTree = ""; }; 4328FAD229D1B35100DABB9A /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/SnackBar.strings; sourceTree = ""; }; 432916632E5736D600445EF5 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ml; path = ml.lproj/TermsOfUse.strings; sourceTree = ""; }; + 43291BE52F9A3CE2002AC772 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/ReaderModeBar.strings; sourceTree = ""; }; 432923F92F599EA4002BF650 /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gd; path = gd.lproj/AppIconSelection.strings; sourceTree = ""; }; 432923FA2F599EA4002BF650 /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gd; path = gd.lproj/ExternalLink.strings; sourceTree = ""; }; 432923FB2F599EA4002BF650 /* gd */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gd; path = gd.lproj/LibraryPanel.strings; sourceTree = ""; }; @@ -6037,6 +6039,8 @@ 437904F2297EA28D00131AB5 /* br */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = br; path = br.lproj/Alerts.strings; sourceTree = ""; }; 437904F3297EA28D00131AB5 /* br */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = br; path = br.lproj/TabsTray.strings; sourceTree = ""; }; 43790BF4292B94C500968A85 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/SearchHeaderTitle.strings; sourceTree = ""; }; + 437922CF2F9A3C6F001A8B3C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/ReaderModeBar.strings; sourceTree = ""; }; + 437922D02F9A3C6F001A8B3C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Summarize.strings; sourceTree = ""; }; 4379EC8B2EBCBB9100C93011 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/SearchZero.strings; sourceTree = ""; }; 4379F313293E0A5A0029C76D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Alerts.strings"; sourceTree = ""; }; 4379F314293E0A5A0029C76D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/TabsTray.strings"; sourceTree = ""; }; @@ -7064,6 +7068,7 @@ 43B44C722F91002700087C2D /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/ReaderModeBar.strings; sourceTree = ""; }; 43B44CF22EBCBA190009A930 /* hy-AM */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hy-AM"; path = "hy-AM.lproj/SearchZero.strings"; sourceTree = ""; }; 43B44F952EBCB996002099A4 /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/SearchZero.strings"; sourceTree = ""; }; + 43B474212F9A3A8B0050C535 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/WorldCup.strings; sourceTree = ""; }; 43B492ED2CF49924006BC7BB /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/SocialMediaApp.strings; sourceTree = ""; }; 43B492EE2CF49925006BC7BB /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/SocialShare.strings; sourceTree = ""; }; 43B497EF2A52E5AE00F39C2D /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/CustomizeFirefoxHome.strings; sourceTree = ""; }; @@ -7072,6 +7077,7 @@ 43B4A4B82A3733ED00D652DB /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Footer.strings; sourceTree = ""; }; 43B4A4B92A3733ED00D652DB /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/RememberCard.strings; sourceTree = ""; }; 43B4A4BA2A3733ED00D652DB /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/UpdateCard.strings; sourceTree = ""; }; + 43B4B6102F9A3AD2000459EA /* hsb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hsb; path = hsb.lproj/WorldCup.strings; sourceTree = ""; }; 43B4BDA12A124AC3007EFE74 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/FirefoxSync.strings; sourceTree = ""; }; 43B4BDA22A124AC3007EFE74 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Upgrade.strings; sourceTree = ""; }; 43B4CE1E2E44C29800C749FC /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Summarizer.strings; sourceTree = ""; }; @@ -7090,6 +7096,7 @@ 43B5B73E2A6E931200F64C51 /* dsb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = dsb; path = dsb.lproj/CustomizeFirefoxHome.strings; sourceTree = ""; }; 43B5B73F2A6E931200F64C51 /* dsb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = dsb; path = dsb.lproj/SelectCreditCard.strings; sourceTree = ""; }; 43B5E7C92DB65F11000C52AD /* ny */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ny; path = ny.lproj/WidgetIntents.strings; sourceTree = ""; }; + 43B5FFFE2F9A3A3C006D9796 /* es-AR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-AR"; path = "es-AR.lproj/WorldCup.strings"; sourceTree = ""; }; 43B627732C3C001E00A188E8 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/EditAddress.strings; sourceTree = ""; }; 43B627752C3C001E00A188E8 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ScanQRCode.strings; sourceTree = ""; }; 43B646422DEDBCF3003B42A7 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Bookmarks.strings; sourceTree = ""; }; @@ -7193,6 +7200,7 @@ 43BB76DD29C87A5A00087F41 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/SnackBar.strings"; sourceTree = ""; }; 43BB7C7129FFD6E9000AFC82 /* kk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kk; path = kk.lproj/Notification.strings; sourceTree = ""; }; 43BB7C7229FFD6EA000AFC82 /* kk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kk; path = kk.lproj/ZoomPageBar.strings; sourceTree = ""; }; + 43BB812F2F9A3BFE001C9B76 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/WorldCup.strings"; sourceTree = ""; }; 43BBB5752B8392F7001D4811 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/KeyboardAccessory.strings"; sourceTree = ""; }; 43BBB5762B8392F7001D4811 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/PasswordAutofill.strings"; sourceTree = ""; }; 43BBBAB02D75C5F80047BA85 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/AppIconSelection.strings; sourceTree = ""; }; @@ -7449,6 +7457,7 @@ 43CD039B2BA859450031362A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/BottomSheet.strings; sourceTree = ""; }; 43CD3C672D5A1643009E68CA /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/ExternalLink.strings; sourceTree = ""; }; 43CD4C742934CF9000753996 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/SearchHeaderTitle.strings; sourceTree = ""; }; + 43CD5E682F9A39F500D00EFC /* dsb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = dsb; path = dsb.lproj/WorldCup.strings; sourceTree = ""; }; 43CD60CC2EA10CFA00AE4479 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Translations.strings"; sourceTree = ""; }; 43CD8C0829F69B6900B3ED1C /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Notification.strings; sourceTree = ""; }; 43CD8C0929F69B6900B3ED1C /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/ZoomPageBar.strings; sourceTree = ""; }; @@ -7600,6 +7609,7 @@ 43D4B09B2B83906900991110 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/PasswordAutofill.strings; sourceTree = ""; }; 43D4BCB92972082400775FB5 /* CreditCardSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardSettingsViewModel.swift; sourceTree = ""; }; 43D4CC042EBCBBD1007D6C97 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/SearchZero.strings; sourceTree = ""; }; + 43D4DAA72F9A3C94001C100D /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/WorldCup.strings; sourceTree = ""; }; 43D504B828B39D48000AB654 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/JumpBackIn.strings; sourceTree = ""; }; 43D504B928B39D48000AB654 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/ToolbarLocation.strings; sourceTree = ""; }; 43D50B5B2B55495600E2E149 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/TabToolbar.strings; sourceTree = ""; }; @@ -8356,6 +8366,7 @@ 43F349362B5549CC00737A91 /* es-AR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-AR"; path = "es-AR.lproj/TabToolbar.strings"; sourceTree = ""; }; 43F362712C453A1900F14754 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/EditAddress.strings; sourceTree = ""; }; 43F362722C453A1900F14754 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/ScanQRCode.strings; sourceTree = ""; }; + 43F374192F9A3B0300FFC0F7 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/WorldCup.strings; sourceTree = ""; }; 43F37875293E0B7C005F1168 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/Alerts.strings; sourceTree = ""; }; 43F37876293E0B7C005F1168 /* ka */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ka; path = ka.lproj/TabsTray.strings; sourceTree = ""; }; 43F3AB252B554C7600CAEC9A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/TabToolbar.strings; sourceTree = ""; }; @@ -8453,6 +8464,9 @@ 43FA499C29C875C0005062DB /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/EditCard.strings; sourceTree = ""; }; 43FA499F29C875C0005062DB /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Settings.strings; sourceTree = ""; }; 43FA49A229C875C0005062DB /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/SnackBar.strings; sourceTree = ""; }; + 43FA61532F9A3CD10065492B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/RelayMask.strings; sourceTree = ""; }; + 43FA61542F9A3CD10065492B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/Summarizer.strings; sourceTree = ""; }; + 43FA61552F9A3CD10065492B /* tt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tt; path = tt.lproj/TermsOfUse.strings; sourceTree = ""; }; 43FA93782DC501CB00C93AE8 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/EditCard.strings; sourceTree = ""; }; 43FA93792DC501CB00C93AE8 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/PasswordGenerator.strings; sourceTree = ""; }; 43FA937A2DC501CB00C93AE8 /* fil */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fil; path = fil.lproj/Settings.strings; sourceTree = ""; }; @@ -8814,8 +8828,6 @@ 612194E22CE507CF001664BB /* WallpaperBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperBackgroundView.swift; sourceTree = ""; }; 612194E52CE50A93001664BB /* WallpaperAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperAction.swift; sourceTree = ""; }; 612194E72CE50A9B001664BB /* WallpaperState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperState.swift; sourceTree = ""; }; - 613489A22D942A020009AF01 /* ToastTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastTelemetry.swift; sourceTree = ""; }; - 613489A42D9443610009AF01 /* ToastTelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastTelemetryTests.swift; sourceTree = ""; }; 61497F3B2F687B5A00BC5ACA /* AIControlsSettingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIControlsSettingTests.swift; sourceTree = ""; }; 61497F3D2F68856000BC5ACA /* MockGeneralSettingsDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGeneralSettingsDelegate.swift; sourceTree = ""; }; 614985472F6B0B6200BC5ACA /* AIControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIControlsModel.swift; sourceTree = ""; }; @@ -10242,8 +10254,8 @@ C1C54E119D804703437D116C /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/3DTouchActions.strings; sourceTree = ""; }; C1DF4F71AB33BAB7B199F361 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/PrivateBrowsing.strings; sourceTree = ""; }; C1F24EFFB6E9B114849A4DB3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/PrivateBrowsing.strings; sourceTree = ""; }; - C209316D2F3CA52E00F0B05C /* VoiceSearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceSearchCoordinator.swift; sourceTree = ""; }; - C20931712F3CAB0100F0B05C /* VoiceSearchCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceSearchCoordinatorTests.swift; sourceTree = ""; }; + C209316D2F3CA52E00F0B05C /* QuickAnswersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickAnswersCoordinator.swift; sourceTree = ""; }; + C20931712F3CAB0100F0B05C /* QuickAnswersCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickAnswersCoordinatorTests.swift; sourceTree = ""; }; C2126EF72F4477EE001C01FE /* SummarizerLanguageExpansionConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummarizerLanguageExpansionConfiguration.swift; sourceTree = ""; }; C2126EF92F447A46001C01FE /* SummarizerNimbusUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummarizerNimbusUtilsTests.swift; sourceTree = ""; }; C21326982F5713F000746393 /* SummarizerLanguageProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummarizerLanguageProviderTests.swift; sourceTree = ""; }; @@ -10332,6 +10344,7 @@ C2D71B962A384F40003DEC7A /* ThemedSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedSubtitleTableViewCell.swift; sourceTree = ""; }; C2D71B982A384F6A003DEC7A /* ThemedLeftAlignedTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedLeftAlignedTableViewCell.swift; sourceTree = ""; }; C2D71B9A2A3850B4003DEC7A /* ThemedTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTableViewCellViewModel.swift; sourceTree = ""; }; + C2D7AC3C2F9B5DC30090EC28 /* WorldCupAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorldCupAction.swift; sourceTree = ""; }; C2D80BE62AADE38100CDF7A9 /* CredentialAutofillCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialAutofillCoordinator.swift; sourceTree = ""; }; C2D80BEA2AAF395200CDF7A9 /* CredentialAutofillCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialAutofillCoordinatorTests.swift; sourceTree = ""; }; C2D80BEC2AAF3C6B00CDF7A9 /* MockBrowserCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBrowserCoordinator.swift; sourceTree = ""; }; @@ -10430,7 +10443,6 @@ C81A8F2426D3ED1900EBA539 /* UIWindow+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIWindow+Extension.swift"; sourceTree = ""; }; C81B78A3280752A20000C15F /* NimbusFeatureFlagLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbusFeatureFlagLayer.swift; sourceTree = ""; }; C81C66C329F00D1000F6422F /* UserActivityRouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityRouteTests.swift; sourceTree = ""; }; - C825E9822832A425006CB811 /* NimbusSearchBarLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbusSearchBarLayer.swift; sourceTree = ""; }; C82A94E4269CB77500624AA7 /* CoreFlaggableFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreFlaggableFeature.swift; sourceTree = ""; }; C82A94E6269CB77F00624AA7 /* LegacyFeatureFlagsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyFeatureFlagsManager.swift; sourceTree = ""; }; C82CDD45233E8996002E2743 /* Tab+ChangeUserAgent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Tab+ChangeUserAgent.swift"; sourceTree = ""; }; @@ -13953,6 +13965,7 @@ 8A7D08E12CAAF79F0035999C /* Homepage */ = { isa = PBXGroup; children = ( + C2D7AC3B2F9B5D750090EC28 /* WorldCup */, 8AD8FAF52EBAFA2400FDFD78 /* HomepageUX.swift */, C7FA9B672E4B9A13004F2CA1 /* Layout */, 8A7C125F2E01DB0600F2D7FA /* SearchBar */, @@ -14059,7 +14072,7 @@ 21FBB4032ADECEFF002D9AB7 /* TabTray */, B236204E2B86C56F000B1DE7 /* AddressAutofillCoordinator.swift */, 0B6153DF2E24E316000D2FBD /* SummarizeCoordinator.swift */, - C209316D2F3CA52E00F0B05C /* VoiceSearchCoordinator.swift */, + C209316D2F3CA52E00F0B05C /* QuickAnswersCoordinator.swift */, ); path = Coordinators; sourceTree = ""; @@ -14089,7 +14102,7 @@ C81C66C329F00D1000F6422F /* UserActivityRouteTests.swift */, 61F7A4322D136C3A00F7317B /* RouteBuilderTests.swift */, 0B6153E12E24ECCA000D2FBD /* SummarizeCoordinatorTests.swift */, - C20931712F3CAB0100F0B05C /* VoiceSearchCoordinatorTests.swift */, + C20931712F3CAB0100F0B05C /* QuickAnswersCoordinatorTests.swift */, ); path = Coordinators; sourceTree = ""; @@ -14243,7 +14256,6 @@ C7F0514F2D95EA5C00EC52C0 /* HistoryDeletionUtilityTelemetryTests.swift */, 0A686B3B2CDB70DC0090E146 /* MainMenuTelemetryTests.swift */, 0ABCD45C2D356006005D704A /* TermsOfServiceTelemetryTests.swift */, - 613489A42D9443610009AF01 /* ToastTelemetryTests.swift */, ED6C230F2DFB4F36005EDE1D /* UserTelemetryTests.swift */, 2109726D2DCA8831001162A2 /* ZoomTelemetryTests.swift */, ); @@ -14974,6 +14986,14 @@ path = ThemedTableViewCells; sourceTree = ""; }; + C2D7AC3B2F9B5D750090EC28 /* WorldCup */ = { + isa = PBXGroup; + children = ( + C2D7AC3C2F9B5DC30090EC28 /* WorldCupAction.swift */, + ); + path = WorldCup; + sourceTree = ""; + }; C2F336E92ECE44740071588A /* Service */ = { isa = PBXGroup; children = ( @@ -15269,7 +15289,6 @@ 254B76092B7B44EE00AB8526 /* NimbusFirefoxSuggestFeatureLayer.swift */, C81B78A3280752A20000C15F /* NimbusFeatureFlagLayer.swift */, C87D8B7F2818333F00A6307D /* NimbusManager.swift */, - C825E9822832A425006CB811 /* NimbusSearchBarLayer.swift */, C88E7A5E2A0554FC0072E638 /* OnboardingFeatureLayer */, E1AF27422A17BCF700CE5991 /* TestData */, ); @@ -16183,6 +16202,7 @@ 43C50B4E2A0BCAA800C6A134 /* FirefoxSync.strings */, 43A7153B2A2DF94F00DD5747 /* Footer.strings */, E4E0BB171AFBC9E4008D6260 /* Info.plist */, + 431854E12F9A3977001BCE78 /* WorldCup.strings */, 43940E7F2F6C129100AEC1AE /* ReaderModeBar.strings */, 43C535D92EC5F2FD00A62D84 /* RelayMask.strings */, 437126942EBCB8C90023653F /* SearchZero.strings */, @@ -16384,7 +16404,6 @@ 8AD08D1427E9198E00B8E907 /* TabsTelemetry.swift */, 8A95FF632B1E969E00AC303D /* TelemetryContextualIdentifier.swift */, EBF47E6F1F7979DF00899189 /* TelemetryWrapper.swift */, - 613489A22D942A020009AF01 /* ToastTelemetry.swift */, ED232E9F2DFA3BF00046DA47 /* UserTelemetry.swift */, 8A0727452B4890B50071BB9F /* WebviewTelemetry.swift */, ); @@ -17833,6 +17852,7 @@ 43937C2329BA686000074633 /* Onboarding.strings in Resources */, D59643EF25C9B8E000EAB8B9 /* PrivateBrowsing.strings in Resources */, 4369B8812B0B75A8003791B9 /* FirefoxHomepage.strings in Resources */, + 431854E32F9A3978001BCE78 /* WorldCup.strings in Resources */, 43C50B532A0BCAA800C6A134 /* Upgrade.strings in Resources */, D59643EB25C9B8E000EAB8B9 /* ClearHistoryConfirm.strings in Resources */, 431F0E082D5A14A800A6C386 /* ExternalLink.strings in Resources */, @@ -18884,7 +18904,6 @@ 8A9B7A8A2F1A120100ABCDEF /* NewsAffordanceHeaderView.swift in Sources */, 8A91D4112F7D6C7800A1B2C3 /* NewsTransitionHeaderCell.swift in Sources */, 8A91D4132F7D6C7900A1B2C3 /* StoryCategoryPickerView.swift in Sources */, - C825E9832832A425006CB811 /* NimbusSearchBarLayer.swift in Sources */, 8AF347DE2CADD1B200624036 /* HomepageState.swift in Sources */, 8A75AF9C2DE8A1CF00B2C592 /* StartAtHomeMiddleware.swift in Sources */, C8DC90C92A0675E70008832B /* MarkupParsingUtility.swift in Sources */, @@ -19016,6 +19035,7 @@ 8A1CBB992BE01839008BE4D4 /* MicrosurveyPromptState.swift in Sources */, C28094312ECF877D0083BC16 /* TranslationsServiceError.swift in Sources */, 23D57E6E25ED6F2700883FAD /* SearchViewController.swift in Sources */, + C2D7AC3D2F9B5DC30090EC28 /* WorldCupAction.swift in Sources */, C82A94F2269F68ED00624AA7 /* LegacyFeatureFlagsManager.swift in Sources */, C8610DAA2A0EBF7100B79FF1 /* OnboardingCardDelegate.swift in Sources */, C7CDBEFD2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift in Sources */, @@ -19057,7 +19077,7 @@ E1549A632D9D268B00895330 /* ReaderModeSettingsButton.swift in Sources */, AB4FB4D52C89FAFD005EF0CC /* BlockedTrackersTableController.swift in Sources */, C8A4137428BE58C900D8EFEA /* WallpaperMetadataCodableProtocol.swift in Sources */, - C209316E2F3CA52E00F0B05C /* VoiceSearchCoordinator.swift in Sources */, + C209316E2F3CA52E00F0B05C /* QuickAnswersCoordinator.swift in Sources */, 8A93080927BFE88F0052167D /* PhotonActionSheetContainerCell.swift in Sources */, C8B41E0A29F0284B00FE218A /* NimbusOnboardingFeatureLayer.swift in Sources */, F85C7F0E2711C556004BDBA4 /* SettingsViewController.swift in Sources */, @@ -19537,7 +19557,6 @@ 8A4490952BF3C42B00E7E682 /* MicrosurveyConfirmationView.swift in Sources */, 1DA6F6512B48B42900BB5AD6 /* WindowEventCoordinator.swift in Sources */, 437A857827E43FE100E42764 /* FxAWebViewTelemetry.swift in Sources */, - 613489A32D942A020009AF01 /* ToastTelemetry.swift in Sources */, C7F051692DB2F38000EC52C0 /* ContextMenuPreviewViewController.swift in Sources */, E1442FD1294782D9003680B0 /* UIModalPresentationStyle+Photon.swift in Sources */, 21EEAA1D2D4005DE00595119 /* NativeErrorPageFeatureFlag.swift in Sources */, @@ -20101,7 +20120,6 @@ 8C61A2812F3371330021C88D /* BrowserViewControllerKVOTests.swift in Sources */, 21FB43CB2E7AF25D00A8818D /* MockHistoryHandler.swift in Sources */, E1EAA5EF2D49182700D3039C /* NavigationBarStateTests.swift in Sources */, - 613489A52D9443610009AF01 /* ToastTelemetryTests.swift in Sources */, E14BF33E2950B1230039758D /* MailProvidersTests.swift in Sources */, C838FD612899A9BB0068F60B /* WallpaperURLProviderTests.swift in Sources */, C25017362ECBAD0A0071506F /* MockRemoteSettingsClient.swift in Sources */, @@ -20325,7 +20343,7 @@ C24A0D2F2F3A7E7E00BF08B7 /* KeyChainAppAttestKeyIDStoreTests.swift in Sources */, ABEF80D92A2F283E003F52C4 /* CreditCardBottomSheetViewModelTests.swift in Sources */, 8AABBD052A0041380089941E /* MockCoordinator.swift in Sources */, - C20931722F3CAB0100F0B05C /* VoiceSearchCoordinatorTests.swift in Sources */, + C20931722F3CAB0100F0B05C /* QuickAnswersCoordinatorTests.swift in Sources */, 8A4880882E68D41F00AD43D5 /* MockGleanPingUploadRequest.swift in Sources */, 8AABBCFF2A0017960089941E /* MockGleanWrapper.swift in Sources */, 21FA8FAE2AE856460013B815 /* TabsCoordinatorTests.swift in Sources */, @@ -21104,6 +21122,7 @@ 432924002F599EA4002BF650 /* gd */, 432A47642F9102260060EAB0 /* sr */, 43346B8D2F93DBCC004A845F /* pl */, + 43FA61542F9A3CD10065492B /* tt */, ); name = Summarizer.strings; sourceTree = ""; @@ -21345,6 +21364,22 @@ name = SelectCreditCard.strings; sourceTree = ""; }; + 431854E12F9A3977001BCE78 /* WorldCup.strings */ = { + isa = PBXVariantGroup; + children = ( + 431854E22F9A3978001BCE78 /* bg */, + 4320E7B22F9A39EA00C305EA /* de */, + 43CD5E682F9A39F500D00EFC /* dsb */, + 43B5FFFE2F9A3A3C006D9796 /* es-AR */, + 43B474212F9A3A8B0050C535 /* fr */, + 43B4B6102F9A3AD2000459EA /* hsb */, + 43F374192F9A3B0300FFC0F7 /* it */, + 43BB812F2F9A3BFE001C9B76 /* pt-PT */, + 43D4DAA72F9A3C94001C100D /* sv */, + ); + name = WorldCup.strings; + sourceTree = ""; + }; 431F0C482AC1A112006D7D49 /* TabLocation.strings */ = { isa = PBXVariantGroup; children = ( @@ -23656,6 +23691,8 @@ 43685B462F910089005F030B /* ja */, 432A47542F9102250060EAB0 /* sr */, 43346B872F93DBCC004A845F /* pl */, + 437922CF2F9A3C6F001A8B3C /* sl */, + 43291BE52F9A3CE2002AC772 /* uk */, ); name = ReaderModeBar.strings; sourceTree = ""; @@ -25057,6 +25094,7 @@ 435A9B002F87C4FB001A7C9A /* es-MX */, 432A47552F9102250060EAB0 /* sr */, 43346B882F93DBCC004A845F /* pl */, + 43FA61532F9A3CD10065492B /* tt */, ); name = RelayMask.strings; path = "Shared/Supporting Files"; @@ -25520,6 +25558,7 @@ 435A9B022F87C4FB001A7C9A /* es-MX */, 432A47632F9102260060EAB0 /* sr */, 43346B8C2F93DBCC004A845F /* pl */, + 437922D02F9A3C6F001A8B3C /* sl */, ); name = Summarize.strings; sourceTree = ""; @@ -25588,6 +25627,7 @@ 435A9B032F87C4FB001A7C9A /* es-MX */, 432A47682F9102260060EAB0 /* sr */, 43346B8E2F93DBCC004A845F /* pl */, + 43FA61552F9A3CD10065492B /* tt */, ); name = TermsOfUse.strings; sourceTree = ""; diff --git a/firefox-ios/Client.xcodeproj/xcshareddata/xcschemes/Fennec.xcscheme b/firefox-ios/Client.xcodeproj/xcshareddata/xcschemes/Fennec.xcscheme index 1bfef100eb1f9..c525c112e6d52 100644 --- a/firefox-ios/Client.xcodeproj/xcshareddata/xcschemes/Fennec.xcscheme +++ b/firefox-ios/Client.xcodeproj/xcshareddata/xcschemes/Fennec.xcscheme @@ -217,20 +217,6 @@ ReferencedContainer = "container:../BrowserKit"> - - - - + + + + + + diff --git a/firefox-ios/Client/AccessoryViewProvider.swift b/firefox-ios/Client/AccessoryViewProvider.swift index c063332fd8c83..968979abe1ded 100644 --- a/firefox-ios/Client/AccessoryViewProvider.swift +++ b/firefox-ios/Client/AccessoryViewProvider.swift @@ -10,7 +10,11 @@ enum AccessoryType { case standard, creditCard, address, login, passwordGenerator, relayEmailMask } -final class AccessoryViewProvider: UIView, Themeable, InjectedThemeUUIDIdentifiable, LegacyFeatureFlaggable, Notifiable { +final class AccessoryViewProvider: UIView, + Themeable, + InjectedThemeUUIDIdentifiable, + UserFeaturePreferenceProvider, + Notifiable { // MARK: - Constants private struct UX { static let accessoryViewHeight: CGFloat = 56 @@ -42,7 +46,7 @@ final class AccessoryViewProvider: UIView, Themeable, InjectedThemeUUIDIdentifia var useRelayMaskClosure: (() -> Void)? private var searchBarPosition: SearchBarPosition { - return featureFlags.getCustomState(for: .searchBarPosition) ?? .bottom + return userPreferences.searchBarPosition } var toolbarItems: [UIBarButtonItem] { diff --git a/firefox-ios/Client/Application/AccessibilityIdentifiers.swift b/firefox-ios/Client/Application/AccessibilityIdentifiers.swift index 5e29af128c1c5..03a695d621b5b 100644 --- a/firefox-ios/Client/Application/AccessibilityIdentifiers.swift +++ b/firefox-ios/Client/Application/AccessibilityIdentifiers.swift @@ -66,6 +66,7 @@ struct AccessibilityIdentifiers { struct WebView { static let documentLoadingLabel = "WebView.documentLoadingLabel" static let automationTestLeakIndicator = "WebView.LeakIndicatorElement" + static let contentView = "contentView" } static let overKeyboardContainer = "Browser.overKeyboardContainer" @@ -246,6 +247,7 @@ struct AccessibilityIdentifiers { struct OtherButtons { static let logoID = "FxHomeLogoID" static let closeButton = "FirefoxHomepage.closeButton" + static let quickAnswersButton = "FirefoxHomepage.quickAnswersButton" } struct MoreButtons { @@ -649,6 +651,7 @@ struct AccessibilityIdentifiers { struct Addresses { static let title = "Addresses" static let addAddress = "Add address" + static let addressCell = "AddressCell" } } diff --git a/firefox-ios/Client/Application/AppDelegate.swift b/firefox-ios/Client/Application/AppDelegate.swift index 6b45470cdf55e..3f433d6142020 100644 --- a/firefox-ios/Client/Application/AppDelegate.swift +++ b/firefox-ios/Client/Application/AppDelegate.swift @@ -13,7 +13,9 @@ import class MozillaAppServices.Viaduct import struct MozillaAppServices.RustAdsClient import enum MozillaAppServices.MozAdsEnvironment -class AppDelegate: UIResponder, UIApplicationDelegate, LegacyFeatureFlaggable { +class AppDelegate: UIResponder, + UIApplicationDelegate, + FeatureFlaggable { let logger = DefaultLogger.shared var notificationCenter: NotificationProtocol = NotificationCenter.default var orientationLock = UIInterfaceOrientationMask.all @@ -29,7 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, LegacyFeatureFlaggable { lazy var themeManager: ThemeManager = DefaultThemeManager( sharedContainerIdentifier: AppInfo.sharedContainerIdentifier, - isNewAppearanceMenuOnClosure: { self.featureFlags.isFeatureEnabled(.appearanceMenu, checking: .buildOnly) } + isNewAppearanceMenuOnClosure: { self.featureFlagsProvider.isEnabled(.appearanceMenu) } ) lazy var documentLogger = DocumentLogger(logger: logger) lazy var appSessionManager: AppSessionProvider = AppSessionManager() @@ -171,7 +173,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, LegacyFeatureFlaggable { /// Prewarm translation resources off the main thread /// This will fetch the translator WASM and model attachments for the device language. /// Running this on a utility QoS to avoid impacting app launch time. - if featureFlags.isFeatureEnabled(.translation, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.translation) { Task(priority: .utility) { await ASTranslationModelsFetcher().prewarmResourcesForStartup() } diff --git a/firefox-ios/Client/Application/AppLaunchUtil.swift b/firefox-ios/Client/Application/AppLaunchUtil.swift index 282179d3c0e2a..ca68c358fface 100644 --- a/firefox-ios/Client/Application/AppLaunchUtil.swift +++ b/firefox-ios/Client/Application/AppLaunchUtil.swift @@ -9,7 +9,7 @@ import Account import Glean import MozillaAppServices -final class AppLaunchUtil: Sendable { +final class AppLaunchUtil: FeatureFlaggable, Sendable { private let logger: Logger private let profile: Profile private let introScreenManager: IntroScreenManager @@ -198,8 +198,7 @@ final class AppLaunchUtil: Sendable { /// enabled from greater than two to 2. See FXIOS-12704 @MainActor private func migrateTopSitesRowNumbers() { - if LegacyFeatureFlagsManager.shared - .isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.homepageSearchBar) { let defaultNumber = TopSitesRowCountSettingsController.defaultNumberOfRows let userNumberOfTopSiteRows = profile.prefs.intForKey( PrefsKeys.NumberOfTopSiteRows diff --git a/firefox-ios/Client/Application/BackgroundFirefoxSuggestIngestUtility.swift b/firefox-ios/Client/Application/BackgroundFirefoxSuggestIngestUtility.swift index 0f6223545e547..b5240b974fe55 100644 --- a/firefox-ios/Client/Application/BackgroundFirefoxSuggestIngestUtility.swift +++ b/firefox-ios/Client/Application/BackgroundFirefoxSuggestIngestUtility.swift @@ -10,7 +10,10 @@ import Storage /// A background utility that downloads and stores new Firefox Suggest /// suggestions when the device is online and connected to power. -final class BackgroundFirefoxSuggestIngestUtility: BackgroundUtilityProtocol, LegacyFeatureFlaggable, @unchecked Sendable { +final class BackgroundFirefoxSuggestIngestUtility: BackgroundUtilityProtocol, + FeatureFlaggable, + UserFeaturePreferenceProvider, + @unchecked Sendable { static let taskIdentifier = "org.mozilla.ios.firefox.suggest.ingest" let firefoxSuggest: RustFirefoxSuggestProtocol @@ -26,7 +29,9 @@ final class BackgroundFirefoxSuggestIngestUtility: BackgroundUtilityProtocol, Le /// Schedules the ingestion task to run when the app is backgrounded. func scheduleTaskOnAppBackground() { - guard featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) else { return } + guard featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled + else { return } + logger.log("Scheduling background ingestion", level: .debug, category: .storage) @@ -70,7 +75,8 @@ final class BackgroundFirefoxSuggestIngestUtility: BackgroundUtilityProtocol, Le /// Registers a launch handler for the background ingestion task. private func setUp() { - guard featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) else { return } + guard featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled + else { return } BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.taskIdentifier, using: nil) { task in task.expirationHandler = { // Interrupt all ongoing storage operations if our diff --git a/firefox-ios/Client/Application/RemoteSettings/Application Services/ASSearchEngineSelector.swift b/firefox-ios/Client/Application/RemoteSettings/Application Services/ASSearchEngineSelector.swift index fc8f5d41f7e3f..ed8754907d4f7 100644 --- a/firefox-ios/Client/Application/RemoteSettings/Application Services/ASSearchEngineSelector.swift +++ b/firefox-ios/Client/Application/RemoteSettings/Application Services/ASSearchEngineSelector.swift @@ -63,7 +63,7 @@ final class ASSearchEngineSelector: ASSearchEngineSelectorProtocol { var searchResultsConfig = try engineSelector.filterEngineConfiguration(userEnvironment: env) - let serverName = remoteSettingsEnv == .stage ? "STAGE" : "PROD" + let serverName: String = remoteSettingsEnv.rawValue.uppercased() let engineLogList = searchResultsConfig.engines.map { let isOptional = $0.optional ? " (OPTIONAL)" : "" return $0.name + isOptional diff --git a/firefox-ios/Client/Application/SceneDelegate.swift b/firefox-ios/Client/Application/SceneDelegate.swift index bed73076a3457..84d8c95c7c779 100644 --- a/firefox-ios/Client/Application/SceneDelegate.swift +++ b/firefox-ios/Client/Application/SceneDelegate.swift @@ -10,7 +10,7 @@ import Common class SceneDelegate: UIResponder, UIWindowSceneDelegate, - LegacyFeatureFlaggable { + FeatureFlaggable { var window: UIWindow? let profile: Profile = AppContainer.shared.resolve() @@ -24,7 +24,7 @@ class SceneDelegate: UIResponder, private let logger: Logger = DefaultLogger.shared private let tabErrorTelemetryHelper = TabErrorTelemetryHelper.shared private var isDeeplinkOptimizationRefactorEnabled: Bool { - return featureFlags.isFeatureEnabled(.deeplinkOptimizationRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.deeplinkOptimizationRefactor) } // MARK: - Connecting / Disconnecting Scenes diff --git a/firefox-ios/Client/Configuration/version.xcconfig b/firefox-ios/Client/Configuration/version.xcconfig index 31b00b15f33b8..94437d11993e9 100644 --- a/firefox-ios/Client/Configuration/version.xcconfig +++ b/firefox-ios/Client/Configuration/version.xcconfig @@ -1 +1 @@ -APP_VERSION = 150.2 \ No newline at end of file +APP_VERSION = 150.3 \ No newline at end of file diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift index 42b049001605f..56f8ebc2093c5 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserCoordinator.swift @@ -33,7 +33,7 @@ final class BrowserCoordinator: BaseCoordinator, SearchEngineSelectionCoordinatorDelegate, TermsOfUseDelegate, ShareSheetCoordinatorDelegate, - LegacyFeatureFlaggable { + FeatureFlaggable { private struct UX { static let searchEnginePopoverSize = CGSize(width: 250, height: 536) } @@ -56,7 +56,7 @@ final class BrowserCoordinator: BaseCoordinator, private var browserIsReady = false private var windowUUID: WindowUUID { return tabManager.windowUUID } private var isDeeplinkOptimiziationRefactorEnabled: Bool { - return featureFlags.isFeatureEnabled(.deeplinkOptimizationRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.deeplinkOptimizationRefactor) } private var isSummarizerOn: Bool { return summarizerNimbusUtils.isSummarizeFeatureToggledOn @@ -957,7 +957,7 @@ final class BrowserCoordinator: BaseCoordinator, let navigationController = DismissableNavigationViewController() let isPad = UIDevice.current.userInterfaceIdiom == .pad let modalPresentationStyle: UIModalPresentationStyle - if featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.tabTrayUIExperiments) { modalPresentationStyle = .fullScreen } else { modalPresentationStyle = isPad ? .fullScreen: .formSheet @@ -986,7 +986,7 @@ final class BrowserCoordinator: BaseCoordinator, } // FXIOS-13305: We don't handle animations properly for synced tabs, so we will use default presentation - if featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) && + if featureFlagsProvider.isEnabled(.tabTrayUIExperiments) && UIDevice.current.userInterfaceIdiom != .pad && selectedPanel != .syncedTabs { guard let tabTrayVC = tabTrayCoordinator.tabTrayViewController else { return } present(navigationController, customTransition: tabTrayVC, style: modalPresentationStyle) @@ -1039,7 +1039,7 @@ final class BrowserCoordinator: BaseCoordinator, browserViewController.removeDocumentLoadingView() } - func showSummarizePanel(_ trigger: SummarizerTrigger, config: SummarizerConfig?) { + func showSummarizePanel(_ trigger: SummarizerTrigger, config: SummarizerConfig?) { guard isSummarizerOn, tabManager.selectedTab?.isFxHomeTab == false, let webView = tabManager.selectedTab?.webView else { return } @@ -1079,6 +1079,25 @@ final class BrowserCoordinator: BaseCoordinator, router.push(shortcutsLibraryViewController) } + func showQuickAnswers() { + guard !childCoordinators.contains(where: { $0 is QuickAnswersCoordinator }) else { return } + let coordinator = QuickAnswersCoordinator( + parentCoordinatorDelegate: self, + windowUUID: windowUUID, + themeManager: themeManager, + router: router + ) { [weak self] navigationType in + switch navigationType { + case .url(let url): + self?.openURLinNewTab(url) + case .searchResult(let query): + self?.browserViewController.openSearchNewTab(query) + } + } + add(child: coordinator) + coordinator.start() + } + func showPrivacyNoticeLink(url: URL) { let linkVC = TermsOfUseLinkViewController( url: url, diff --git a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift index 1a5c6aa8ebd3f..438576d57a741 100644 --- a/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift +++ b/firefox-ios/Client/Coordinators/Browser/BrowserNavigationHandler.swift @@ -121,6 +121,9 @@ protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler { @MainActor func showShortcutsLibrary() + @MainActor + func showQuickAnswers() + @MainActor func showPrivacyNoticeLink(url: URL) diff --git a/firefox-ios/Client/Coordinators/EnhancedTrackingProtectionCoordinator.swift b/firefox-ios/Client/Coordinators/EnhancedTrackingProtectionCoordinator.swift index a9669637e6a81..9772469d9d725 100644 --- a/firefox-ios/Client/Coordinators/EnhancedTrackingProtectionCoordinator.swift +++ b/firefox-ios/Client/Coordinators/EnhancedTrackingProtectionCoordinator.swift @@ -21,7 +21,7 @@ protocol ETPCoordinatorSSLStatusDelegate: AnyObject { class EnhancedTrackingProtectionCoordinator: BaseCoordinator, TrackingProtectionMenuDelegate, EnhancedTrackingProtectionMenuDelegate, - LegacyFeatureFlaggable { + FeatureFlaggable { private struct UX { static let popoverPreferredSize = CGSize(width: 480, height: 540) } @@ -32,7 +32,7 @@ class EnhancedTrackingProtectionCoordinator: BaseCoordinator, private var enhancedTrackingProtectionMenuVC: TrackingProtectionViewController? weak var parentCoordinator: EnhancedTrackingProtectionCoordinatorDelegate? private var trackingProtectionRefactorStatus: Bool { - featureFlags.isFeatureEnabled(.trackingProtectionRefactor, checking: .buildOnly) + featureFlagsProvider.isEnabled(.trackingProtectionRefactor) } private var trackingProtectionNavController: UINavigationController? diff --git a/firefox-ios/Client/Coordinators/LaunchView/LaunchScreenViewController.swift b/firefox-ios/Client/Coordinators/LaunchView/LaunchScreenViewController.swift index a0a2036f4ca54..6a31201ea4e51 100644 --- a/firefox-ios/Client/Coordinators/LaunchView/LaunchScreenViewController.swift +++ b/firefox-ios/Client/Coordinators/LaunchView/LaunchScreenViewController.swift @@ -5,7 +5,7 @@ import Common import Foundation -class LaunchScreenViewController: UIViewController, LaunchFinishedLoadingDelegate, LegacyFeatureFlaggable { +class LaunchScreenViewController: UIViewController, LaunchFinishedLoadingDelegate, FeatureFlaggable { private lazy var launchScreen = LaunchScreenView.fromNib() private weak var coordinator: LaunchFinishedLoadingDelegate? private var viewModel: LaunchScreenViewModel @@ -14,7 +14,7 @@ class LaunchScreenViewController: UIViewController, LaunchFinishedLoadingDelegat private let nimbusSplashScreenFeatureLayer = NimbusSplashScreenFeatureLayer() private var shouldTriggerSplashScreenExperiment: Bool { - return featureFlags.isFeatureEnabled(.splashScreen, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.splashScreen) && !viewModel.getSplashScreenExperimentHasShown() } diff --git a/firefox-ios/Client/Coordinators/VoiceSearchCoordinator.swift b/firefox-ios/Client/Coordinators/QuickAnswersCoordinator.swift similarity index 100% rename from firefox-ios/Client/Coordinators/VoiceSearchCoordinator.swift rename to firefox-ios/Client/Coordinators/QuickAnswersCoordinator.swift diff --git a/firefox-ios/Client/Coordinators/Scene/SceneCoordinator.swift b/firefox-ios/Client/Coordinators/Scene/SceneCoordinator.swift index 2a571fb625dd3..7d86ec4340219 100644 --- a/firefox-ios/Client/Coordinators/Scene/SceneCoordinator.swift +++ b/firefox-ios/Client/Coordinators/Scene/SceneCoordinator.swift @@ -10,11 +10,11 @@ import OnboardingKit class SceneCoordinator: BaseCoordinator, LaunchCoordinatorDelegate, LaunchFinishedLoadingDelegate, - LegacyFeatureFlaggable { + FeatureFlaggable { var window: UIWindow? var windowUUID: WindowUUID { reservedWindowUUID.uuid } private var isDeeplinkOptimizationRefactorEnabled: Bool { - return featureFlags.isFeatureEnabled(.deeplinkOptimizationRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.deeplinkOptimizationRefactor) } private let screenshotService: ScreenshotService private let sceneContainer: SceneContainer diff --git a/firefox-ios/Client/Coordinators/SettingsCoordinator.swift b/firefox-ios/Client/Coordinators/SettingsCoordinator.swift index 52b5b74321fa7..fbb8de7f26be3 100644 --- a/firefox-ios/Client/Coordinators/SettingsCoordinator.swift +++ b/firefox-ios/Client/Coordinators/SettingsCoordinator.swift @@ -32,7 +32,7 @@ final class SettingsCoordinator: BaseCoordinator, BrowsingSettingsDelegate, AppearanceSettingsDelegate, TranslationPickerSettingsDelegate, - LegacyFeatureFlaggable { + FeatureFlaggable { var settingsViewController: AppSettingsScreen? private let wallpaperManager: WallpaperManagerInterface private let profile: Profile @@ -202,7 +202,7 @@ final class SettingsCoordinator: BaseCoordinator, case .toolbar: let viewModel = SearchBarSettingsViewModel(prefs: profile.prefs) - return LegacyFeatureFlagsManager.shared.isFeatureEnabled(.addressBarMenu, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.addressBarMenu) ? UIHostingController( rootView: AddressBarSettingsView( windowUUID: windowUUID, @@ -431,7 +431,7 @@ final class SettingsCoordinator: BaseCoordinator, func pressedToolbar() { let viewModel = SearchBarSettingsViewModel(prefs: profile.prefs) - if LegacyFeatureFlagsManager.shared.isFeatureEnabled(.addressBarMenu, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.addressBarMenu) { let viewController = UIHostingController( rootView: AddressBarSettingsView( windowUUID: windowUUID, @@ -481,7 +481,7 @@ final class SettingsCoordinator: BaseCoordinator, } private func translationSettingsViewController() -> UIViewController { - if featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.translationLanguagePicker) { let viewController = TranslationPickerSettingsViewController(windowUUID: windowUUID) viewController.coordinator = self return viewController diff --git a/firefox-ios/Client/Experiments/initial_experiments.json b/firefox-ios/Client/Experiments/initial_experiments.json index 0002df860ad52..fd7323767a2f3 100644 --- a/firefox-ios/Client/Experiments/initial_experiments.json +++ b/firefox-ios/Client/Experiments/initial_experiments.json @@ -96,7 +96,7 @@ "channel": "release", "userFacingName": "iOS - Japan Onboarding with Video - Release Prod", "userFacingDescription": "Japan Onboarding with Video", - "isEnrollmentPaused": false, + "isEnrollmentPaused": true, "isRollout": false, "bucketConfig": { "randomizationUnit": "nimbus_id", @@ -159,7 +159,7 @@ ], "targeting": "(app_version|versionCompare('149.3.0') >= 0) && (language in ['ja'])", "startDate": "2026-04-03", - "enrollmentEndDate": null, + "enrollmentEndDate": "2026-04-24", "endDate": null, "proposedDuration": 28, "proposedEnrollment": 14, diff --git a/firefox-ios/Client/FeatureFlags/FeatureFlagID.swift b/firefox-ios/Client/FeatureFlags/FeatureFlagID.swift index add35b092c752..121a59d320ca7 100644 --- a/firefox-ios/Client/FeatureFlags/FeatureFlagID.swift +++ b/firefox-ios/Client/FeatureFlags/FeatureFlagID.swift @@ -15,7 +15,6 @@ enum FeatureFlagID: String, CaseIterable { case appIconSelection case badCertDomainErrorPage case bookmarksSearchFeature - case bottomSearchBar case deeplinkOptimizationRefactor case downloadLiveActivities case firefoxJpGuideDefaultSite @@ -43,6 +42,7 @@ enum FeatureFlagID: String, CaseIterable { case hostedSummarizer case hostedSummarizerToolbarEntrypoint case hostedSummarizerShakeGesture + case httpsUpgrade case improvedAppStoreReviewTriggerFeature case summarizerAppAttestAuth case summarizerLanguageExpansion @@ -60,6 +60,7 @@ enum FeatureFlagID: String, CaseIterable { case trendingSearches case unifiedSearch case videoIntroOnboarding + case worldCupWidget case quickAnswers // Add flags here if you want to toggle them in the `FeatureFlagsDebugViewController`. Add in alphabetical order. @@ -77,6 +78,7 @@ enum FeatureFlagID: String, CaseIterable { .homepageSearchBar, .homepageStoryCategories, .hostedSummarizer, + .httpsUpgrade, .improvedAppStoreReviewTriggerFeature, .microsurvey, .nativeErrorPage, @@ -97,7 +99,8 @@ enum FeatureFlagID: String, CaseIterable { .translation, .translationLanguagePicker, .trendingSearches, - .unifiedSearch: + .unifiedSearch, + .worldCupWidget: return rawValue + PrefsKeys.FeatureFlags.DebugSuffixKey default: return nil @@ -108,6 +111,5 @@ enum FeatureFlagID: String, CaseIterable { /// This enum is a constraint for any feature flag options that have more than /// just an ON or OFF setting. These option must also be added to `FeatureFlagID` enum FeatureFlagIDWithCustomOptions { - case searchBarPosition case startAtHome } diff --git a/firefox-ios/Client/FeatureFlags/LegacyFeatureFlagsManager.swift b/firefox-ios/Client/FeatureFlags/LegacyFeatureFlagsManager.swift index 4ca9611939c6e..107189a0f7548 100644 --- a/firefox-ios/Client/FeatureFlags/LegacyFeatureFlagsManager.swift +++ b/firefox-ios/Client/FeatureFlags/LegacyFeatureFlagsManager.swift @@ -87,14 +87,12 @@ class LegacyFeatureFlagsManager: HasNimbusFeatureFlagLayer, @unchecked Sendable guard let userSetting = feature.getUserPreference(using: nimbusFlags) else { return nil } switch featureID { - case .searchBarPosition: return SearchBarPosition(rawValue: userSetting) as? T case .startAtHome: return StartAtHome(rawValue: userSetting) as? T } } private func convertCustomIDToStandard(_ featureID: FeatureFlagIDWithCustomOptions) -> FeatureFlagID { switch featureID { - case .searchBarPosition: return .bottomSearchBar case .startAtHome: return .startAtHome } } @@ -132,10 +130,6 @@ class LegacyFeatureFlagsManager: HasNimbusFeatureFlagLayer, @unchecked Sendable let feature = NimbusFlaggableFeature(withID: convertCustomIDToStandard(featureID), and: profile) switch featureID { - case .searchBarPosition: - if let option = desiredState as? SearchBarPosition { - feature.setUserPreference(to: option.rawValue) - } case .startAtHome: if let option = desiredState as? StartAtHome { feature.setUserPreference(to: option.rawValue) diff --git a/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift b/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift index 2a8a8be7fc4b7..3b175eae8a0ee 100644 --- a/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift +++ b/firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift @@ -7,7 +7,7 @@ import Foundation import Shared import UIKit -struct NimbusFlaggableFeature: HasNimbusSearchBar { +struct NimbusFlaggableFeature { // MARK: - Variables private let profile: Profile private var featureID: FeatureFlagID @@ -18,8 +18,6 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { switch featureID { case .aiKillSwitch: return PrefsKeys.Settings.aiKillSwitchFeature - case .bottomSearchBar: - return FlagKeys.SearchBarPosition case .firefoxSuggestFeature: return FlagKeys.FirefoxSuggest case .homepageBookmarksSectionDefault: @@ -61,6 +59,7 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { .hostedSummarizer, .hostedSummarizerToolbarEntrypoint, .hostedSummarizerShakeGesture, + .httpsUpgrade, .quickAnswers, .relayIntegration, .summarizerAppAttestAuth, @@ -78,7 +77,8 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { .translationLanguagePicker, .trendingSearches, .unifiedSearch, - .videoIntroOnboarding: + .videoIntroOnboarding, + .worldCupWidget: return nil } } @@ -134,10 +134,8 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { } switch featureID { - case .bottomSearchBar: - return nimbusSearchBar.getDefaultPosition().rawValue case .splashScreen: - return nimbusSearchBar.getDefaultPosition().rawValue + return SearchBarPosition.bottom.rawValue case .startAtHome: return FxNimbus.shared.features.startAtHomeFeature.value().setting.rawValue default: return nil @@ -168,15 +166,8 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar { /// in the `featureKey()` function - with which to write to UserDefaults, then the /// feature cannot be turned on/off. public func setUserPreference(to option: String) { - guard !option.isEmpty, - let optionsKey = featureKey - else { return } - - switch featureID { - case .bottomSearchBar: - profile.prefs.setString(option, forKey: optionsKey) - - default: break - } + guard !option.isEmpty else { return } + // TODO: to be removed with 15192 + // no-op for now } } diff --git a/firefox-ios/Client/FeatureFlags/UserFeaturePreferenceManager.swift b/firefox-ios/Client/FeatureFlags/UserFeaturePreferenceManager.swift index b800cf8348a04..b4b7853502754 100644 --- a/firefox-ios/Client/FeatureFlags/UserFeaturePreferenceManager.swift +++ b/firefox-ios/Client/FeatureFlags/UserFeaturePreferenceManager.swift @@ -34,23 +34,20 @@ protocol UserFeaturePreferring: Sendable { final class UserFeaturePreferenceManager: UserFeaturePreferring, @unchecked Sendable { private let prefs: Prefs private let nimbusLayer: NimbusFeatureFlagLayer - private let nimbusSearchBar: NimbusSearchBarLayer init( prefs: Prefs, - nimbusLayer: NimbusFeatureFlagLayer = NimbusManager.shared.featureFlagLayer, - nimbusSearchBar: NimbusSearchBarLayer = NimbusManager.shared.bottomSearchBarLayer + nimbusLayer: NimbusFeatureFlagLayer = NimbusManager.shared.featureFlagLayer ) { self.prefs = prefs self.nimbusLayer = nimbusLayer - self.nimbusSearchBar = nimbusSearchBar } // MARK: - Bool preferences var isAIKillSwitchEnabled: Bool { - prefs.boolForKey(PrefsKeys.Settings.aiKillSwitchFeature) - ?? nimbusLayer.checkNimbusConfigFor(.aiKillSwitch) + // Even when this feature is on in Nimbus, the user preference default value should be false + prefs.boolForKey(PrefsKeys.Settings.aiKillSwitchFeature) ?? false } var isFirefoxSuggestEnabled: Bool { @@ -85,7 +82,7 @@ final class UserFeaturePreferenceManager: UserFeaturePreferring, @unchecked Send let position = SearchBarPosition(rawValue: raw) { return position } - return nimbusSearchBar.getDefaultPosition() + return .top } var startAtHomeSetting: StartAtHome { @@ -135,11 +132,11 @@ final class UserFeaturePreferenceManager: UserFeaturePreferring, @unchecked Send /// Adopt this protocol to access user feature preferences via AppContainer. /// Replaces FeatureFlaggable for user preference checks. -protocol HasUserFeaturePreferences { +protocol UserFeaturePreferenceProvider { var userPreferences: UserFeaturePreferring { get } } -extension HasUserFeaturePreferences { +extension UserFeaturePreferenceProvider { var userPreferences: UserFeaturePreferring { AppContainer.shared.resolve() } diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift index 18620d2805299..f484aacbf57d7 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressAutofillSettingsView.swift @@ -64,3 +64,37 @@ struct AddressAutofillSettingsView: View { viewBackground = Color(color.layer1) } } + +struct ListHeaderPadding: ViewModifier { + let isLandscape: Bool + let paddingSize: CGFloat + + func body(content: Content) -> some View { + let isPad = UIDevice().userInterfaceIdiom == .pad + let shouldAddPadding = !isLandscape || (isPad && isLandscape) + if #available(iOS 26.0, *) { + if shouldAddPadding { + content.padding(.leading, paddingSize) + } else { + content + } + } else { + content + } + } +} + +struct ListItemIconPadding: ViewModifier { + let isLandscape: Bool + let paddingSize: CGFloat + + func body(content: Content) -> some View { + let isPad = UIDevice().userInterfaceIdiom == .pad + let shouldAddPadding = !isLandscape || (isPad && isLandscape) + if #available(iOS 26.0, *), shouldAddPadding { + content.padding(.leading, paddingSize) + } else { + content + } + } +} diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift index 50fa0a29e8cd9..ac3f07468eceb 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressCellView.swift @@ -11,6 +11,15 @@ import struct MozillaAppServices.Address /// A view representing a cell displaying address information. struct AddressCellView: View { + // MARK: - Constants + + private enum UX { + static let listIconPadding: CGFloat = -8 + static let hStackSpacing: CGFloat = 24 + static let vStackSpacing: CGFloat = 0 + static let dividerHeight: CGFloat = 1 + } + // MARK: - Properties let windowUUID: WindowUUID @@ -20,6 +29,12 @@ struct AddressCellView: View { @State private var textColor: Color = .clear @State private var customLightGray: Color = .clear @State private var iconPrimary: Color = .clear + @State private var backgroundColor: Color = .clear + @State private var highlightColor: Color = .clear + + @State private var isHighlighted = false + @GestureState private var isPressing = false + @State private var isLandscape = false private(set) var address: Address private(set) var onTap: () -> Void @@ -27,41 +42,63 @@ struct AddressCellView: View { // MARK: - Body var body: some View { - Button(action: onTap) { - VStack(alignment: .leading, spacing: 0) { - HStack(alignment: .midIconAndLabel, spacing: 24) { - Image(StandardImageIdentifiers.Large.location) - .renderingMode(.template) - .padding(.leading, 16) - .foregroundColor(iconPrimary) - .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } - VStack(alignment: .leading) { - if !address.name.isEmpty { - Text(address.name) - .font(.body) - .foregroundColor(textColor) - .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } - } - if !address.streetAddress.isEmpty { - Text(address.streetAddress) - .font(.subheadline) - .foregroundColor(customLightGray) - } - if !address.addressCityStateZipcode.isEmpty { - Text(address.addressCityStateZipcode) - .font(.subheadline) - .foregroundColor(customLightGray) - } + VStack(spacing: UX.vStackSpacing) { + HStack(alignment: .midIconAndLabel, spacing: UX.hStackSpacing) { + Image(decorative: StandardImageIdentifiers.Large.location) + .renderingMode(.template) + .modifier(ListItemIconPadding(isLandscape: isLandscape, + paddingSize: UX.listIconPadding)) + .foregroundColor(iconPrimary) + .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } + VStack(alignment: .leading) { + if !address.name.isEmpty { + Text(address.name) + .font(.body) + .foregroundColor(textColor) + .alignmentGuide(.midIconAndLabel) { $0[VerticalAlignment.center] } + } + if !address.streetAddress.isEmpty { + Text(address.streetAddress) + .font(.subheadline) + .foregroundColor(customLightGray) + } + if !address.addressCityStateZipcode.isEmpty { + Text(address.addressCityStateZipcode) + .font(.subheadline) + .foregroundColor(customLightGray) } - Spacer() } + Spacer() } .padding() - Spacer().frame(height: 0) - Divider().frame(height: 1) + Divider().frame(height: UX.dividerHeight) + } + .contentShape(Rectangle()) + .onChange(of: isPressing) { pressing in + if pressing { + isHighlighted = true + } else { + withAnimation(.easeOut(duration: 0.2)) { + isHighlighted = false + } + } } + .gesture( + DragGesture(minimumDistance: 0) + .updating($isPressing) { _, state, _ in state = true } + .onEnded { value in + let isWithinTapBounds = abs(value.translation.width) < 20 + && abs(value.translation.height) < 20 + if isWithinTapBounds { + onTap() + } + } + ) .listRowInsets(EdgeInsets()) - .buttonStyle(AddressButtonStyle(theme: themeManager.getCurrentTheme(for: windowUUID))) + .listRowBackground( + (isHighlighted ? highlightColor : backgroundColor) + .edgesIgnoringSafeArea([.leading, .trailing]) + ) .listRowSeparator(.hidden) .onAppear { applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) @@ -70,7 +107,14 @@ struct AddressCellView: View { guard let uuid = notification.windowUUID, uuid == windowUUID else { return } applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + isLandscape = UIDevice.current.orientation.isLandscape + } + .accessibilityElement(children: .ignore) + .accessibilityIdentifier(AccessibilityIdentifiers.Settings.Address.Addresses.addressCell) .accessibilityLabel(address.a11ySettingsRow) + .accessibilityAddTraits(.isButton) + .accessibilityAction { onTap() } } // MARK: - Theme Application @@ -82,18 +126,7 @@ struct AddressCellView: View { textColor = Color(color.textPrimary) customLightGray = Color(color.textSecondary) iconPrimary = Color(color.iconPrimary) - } -} - -// MARK: - CustomButtonStyle - -/// A address button style with a specific theme. -struct AddressButtonStyle: ButtonStyle { - let theme: Theme - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .background(configuration.isPressed ? Color(theme.colors.layer1) : Color(theme.colors.layer2)) - .foregroundColor(.white) + backgroundColor = Color(color.layer2) + highlightColor = Color(color.layer5Hover) } } diff --git a/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift b/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift index 54068b21eec56..4b159196d5c89 100644 --- a/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift +++ b/firefox-ios/Client/Frontend/Autofill/Address/AddressListView.swift @@ -19,6 +19,7 @@ struct AddressListView: View { static let titleFontSize: CGFloat = 22 static let subtitleFontSize: CGFloat = 16 static let contentUnavailableViewTopPadding: CGFloat = 125 + static let listPadding: CGFloat = -8 } // MARK: - Properties @@ -34,13 +35,17 @@ struct AddressListView: View { @State var imageColor: Color = .clear @State var listColor: Color = .clear + @State private var isLandscape = false + // MARK: - Body var body: some View { Group { if viewModel.showSection { List { - Section(header: Text(String.Addresses.Settings.SavedAddressesSectionTitle)) { + Section(header: Text(String.Addresses.Settings.SavedAddressesSectionTitle) + .modifier(ListHeaderPadding(isLandscape: isLandscape, + paddingSize: UX.listPadding))) { ForEach(viewModel.addresses, id: \.self) { address in AddressCellView( windowUUID: windowUUID, @@ -86,6 +91,9 @@ struct AddressListView: View { guard let uuid = notification.windowUUID, uuid == windowUUID else { return } applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + isLandscape = UIDevice.current.orientation.isLandscape + } .onDisappear { viewModel.editAddressWebViewManager.teardownWebView() } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift index d97f232d66a71..505f27607df8e 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Actions/NavigationBrowserAction.swift @@ -38,7 +38,9 @@ enum NavigationBrowserActionType: ActionType { case tapOnBookmarksShowMoreButton case tapOnHomepageSearchBar case tapOnShortcutsShowAllButton + case tapOnQuickAnswersButton case tapOnPrivacyNoticeLink case tapOnShowCertificatesFromErrorPage case tapOnNativeErrorPageLearnMore + case navigationDestinationHandled } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift index 9ff3fa0f0b6a6..5e4141dafc85c 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+DownloadQueueDelegate.swift @@ -26,7 +26,7 @@ extension BrowserViewController: DownloadQueueDelegate { self.downloadProgressManager = downloadProgressManager if #available(iOS 17, *), - featureFlags.isFeatureEnabled(.downloadLiveActivities, checking: .buildOnly), + featureFlagsProvider.isEnabled(.downloadLiveActivities), tabManager.selectedTab?.isPrivate == false { let downloadLiveActivityWrapper = DownloadLiveActivityWrapper( downloadProgressManager: downloadProgressManager, @@ -43,7 +43,7 @@ extension BrowserViewController: DownloadQueueDelegate { private func dismissDownloadLiveActivity() { if #available(iOS 17, *), - featureFlags.isFeatureEnabled(.downloadLiveActivities, checking: .buildOnly), + featureFlagsProvider.isEnabled(.downloadLiveActivities), let downloadLiveActivityWrapper = self.downloadLiveActivityWrapper { downloadLiveActivityWrapper.end(durationToDismissal: .none) self.downloadLiveActivityWrapper = nil diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+ToolBarActionMenuDelegate.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+ToolBarActionMenuDelegate.swift index fcc4854fbdc5e..dc92ff1a4930c 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+ToolBarActionMenuDelegate.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+ToolBarActionMenuDelegate.swift @@ -239,28 +239,7 @@ extension BrowserViewController: PhotonActionSheetProtocol { ) ) self.updateTabCountUsingTabManager(self.tabManager) - - if !self.featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) - || UIDevice.current.userInterfaceIdiom == .pad { - self.showLegacyCloseTabToast(message: .TabsTray.CloseTabsToast.SingleTabTitle) - } } }.items } - - /// This toast was tied into the legacy main menu, moved it to it's own function. - /// New toasts should be piped through Redux. - func showLegacyCloseTabToast(message: String) { - let viewModel = ButtonToastViewModel(labelText: message, - buttonText: .UndoString) - let toast = ButtonToast(viewModel: viewModel, - theme: currentTheme()) { [weak self] isButtonTapped in - guard let self, - tabManager.backupCloseTab != nil, - isButtonTapped - else { return } - self.tabManager.undoCloseTab() - } - show(toast: toast) - } } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift index ef127c512a77f..6db680202cfb8 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Extensions/BrowserViewController+WebViewDelegates.swift @@ -270,7 +270,14 @@ extension BrowserViewController: WKUIDelegate { let previewViewController = ContextMenuPreviewViewController() previewViewController.view.isUserInteractionEnabled = false - let clonedWebView = WKWebView(frame: webView.frame, configuration: webView.configuration) + + let configuration = webView.configuration + // Ensures the preview doesn't autoplay media [FXIOS-15375] + configuration.allowsInlineMediaPlayback = false + configuration.allowsPictureInPictureMediaPlayback = false + configuration.mediaTypesRequiringUserActionForPlayback = .all + configuration.allowsAirPlayForMediaPlayback = false + let clonedWebView = WKWebView(frame: webView.frame, configuration: configuration) previewViewController.view.addSubview(clonedWebView) clonedWebView.pinToSuperview() diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift index 540ffca106a92..c4fbf55c83000 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserNavigationType.swift @@ -17,6 +17,7 @@ enum BrowserNavigationDestination: Equatable { case homepageZeroSearch case zeroSearch case shortcutsLibrary + case quickAnswers case privacyNoticeLink(URL) case summarizer(config: SummarizerConfig, trigger: SummarizerTrigger) case certificatesFromErrorPage diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift index 82152674f8250..3bfda9d43f471 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/State/BrowserViewControllerState.swift @@ -187,6 +187,7 @@ struct BrowserViewControllerState: ScreenState { NavigationBrowserActionType.tapOnShareSheet, NavigationBrowserActionType.tapOnHomepageSearchBar, NavigationBrowserActionType.tapOnShortcutsShowAllButton, + NavigationBrowserActionType.tapOnQuickAnswersButton, NavigationBrowserActionType.tapOnPrivacyNoticeLink, NavigationBrowserActionType.tapOnShowCertificatesFromErrorPage, NavigationBrowserActionType.tapOnNativeErrorPageLearnMore: @@ -199,6 +200,16 @@ struct BrowserViewControllerState: ScreenState { autoTranslatePromptState: AutoTranslatePromptState.reducer(state.autoTranslatePromptState, action), navigationDestination: action.navigationDestination ) + case NavigationBrowserActionType.navigationDestinationHandled: + return BrowserViewControllerState( + searchScreenState: state.searchScreenState, + windowUUID: state.windowUUID, + shouldShowReaderModeBarSummarizerButton: state.shouldShowReaderModeBarSummarizerButton, + browserViewType: state.browserViewType, + microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action), + autoTranslatePromptState: AutoTranslatePromptState.reducer(state.autoTranslatePromptState, action), + navigationDestination: nil + ) default: return passthroughState(from: state, action: action) } diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index 972c68d64c4f9..bd19cd4c788c3 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -37,7 +37,8 @@ class BrowserViewController: UIViewController, NavigationToolbarContainerDelegate, AddressToolbarContainerDelegate, BookmarksHandlerDelegate, - LegacyFeatureFlaggable, + LegacyFeatureFlaggable, // TODO: ROUX remove with 15192 + FeatureFlaggable, CanRemoveQuickActionBookmark, BrowserStatusBarScrollDelegate, LegacyTabScrollController.Delegate { @@ -281,14 +282,12 @@ class BrowserViewController: UIViewController, // MARK: Feature flags private var isTabTrayUIExperimentsEnabled: Bool { - let flagToCheck = FeatureFlagID.tabTrayUIExperiments - let featureFlagStatus = featureFlags.isFeatureEnabled(flagToCheck, checking: .buildOnly) + let featureFlagStatus = featureFlagsProvider.isEnabled(.tabTrayUIExperiments) return featureFlagStatus && UIDevice.current.userInterfaceIdiom != .pad } var isUnifiedSearchEnabled: Bool { - let flagToCheck = FeatureFlagID.unifiedSearch - return featureFlags.isFeatureEnabled(flagToCheck, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.unifiedSearch) } var isSwipingTabsEnabled: Bool { @@ -296,8 +295,7 @@ class BrowserViewController: UIViewController, } var isToolbarUpdateHintEnabled: Bool { - let flagToCheck = FeatureFlagID.toolbarUpdateHint - return featureFlags.isFeatureEnabled(flagToCheck, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.toolbarUpdateHint) } var isNativeErrorPageEnabled: Bool { @@ -313,11 +311,11 @@ class BrowserViewController: UIViewController, } var isDeeplinkOptimizationRefactorEnabled: Bool { - return featureFlags.isFeatureEnabled(.deeplinkOptimizationRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.deeplinkOptimizationRefactor) } var isHomepageSearchBarEnabled: Bool { - return featureFlags.isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.homepageSearchBar) } var isSummarizerToolbarFeatureEnabled: Bool { @@ -325,12 +323,12 @@ class BrowserViewController: UIViewController, } var isTabScrollRefactoringEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabScrollRefactorFeature, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabScrollRefactorFeature) } private var isRecentOrTrendingSearchEnabled: Bool { - let isTrendingSearchEnabled = featureFlags.isFeatureEnabled(.trendingSearches, checking: .buildOnly) - let isRecentSearchEnabled = featureFlags.isFeatureEnabled(.recentSearches, checking: .buildOnly) + let isTrendingSearchEnabled = featureFlagsProvider.isEnabled(.trendingSearches) + let isRecentSearchEnabled = featureFlagsProvider.isEnabled(.recentSearches) return isTrendingSearchEnabled || isRecentSearchEnabled } @@ -340,17 +338,16 @@ class BrowserViewController: UIViewController, } var isSnapKitRemovalEnabled: Bool { - return featureFlags.isFeatureEnabled(.snapkitRemovalRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.snapkitRemovalRefactor) } var isAppStoreReviewTriggerEnabled: Bool { - return featureFlags.isFeatureEnabled(.improvedAppStoreReviewTriggerFeature, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.improvedAppStoreReviewTriggerFeature) } // MARK: Computed vars lazy var isBottomSearchBar: Bool = { - guard isSearchBarLocationFeatureEnabled else { return false } return searchBarPosition == .bottom }() @@ -564,6 +561,7 @@ class BrowserViewController: UIViewController, updateHeaderConstraints() addressToolbarContainer.updateConstraints() updateMicrosurveyConstraints() + updateAutoTranslatePromptConstraints() updateToolbarDisplay() addOrUpdateMaskViewIfNeeded() @@ -700,7 +698,7 @@ class BrowserViewController: UIViewController, } private func updateAddressToolbarContainerPosition(for traitCollection: UITraitCollection) { - guard searchBarPosition == .bottom, isSearchBarLocationFeatureEnabled else { return } + guard searchBarPosition == .bottom else { return } let isNavToolbar = toolbarHelper.shouldShowNavigationToolbar(for: traitCollection) let newPosition: SearchBarPosition = isNavToolbar ? .bottom : .top @@ -944,7 +942,7 @@ class BrowserViewController: UIViewController, executeNavigationAndDisplayActions() handleMicrosurvey(state: state) - if featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.translationLanguagePicker) { handleAutoTranslatePrompt(state: state) } @@ -1240,7 +1238,7 @@ class BrowserViewController: UIViewController, /// As part of the homepage search bar work, we want to only hide the toolbar when the homepage search bar appears. /// The homepage search bar should not appear if we are in editing mode. private func shouldHideAddressToolbar() { - guard featureFlags.isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) else { return } + guard featureFlagsProvider.isEnabled(.homepageSearchBar) else { return } let toolbarState = store.state.componentState( ToolbarState.self, for: .toolbar, @@ -1940,7 +1938,7 @@ class BrowserViewController: UIViewController, } // FXIOS-14783: Experimentation on removing this code, do not add anything in there - if !featureFlags.isFeatureEnabled(.needsReloadRefactor, checking: .buildOnly) { + if !featureFlagsProvider.isEnabled(.needsReloadRefactor) { if webView.url == nil, selectedTab.url?.absoluteString != "about:blank" { // The web view can go gray if it was zombified due to memory pressure. // When this happens, the URL is nil, so try restoring the page upon selection. @@ -1987,7 +1985,7 @@ class BrowserViewController: UIViewController, // MARK: - Microsurvey private func setupMicrosurvey() { - guard featureFlags.isFeatureEnabled(.microsurvey, checking: .buildOnly), microsurvey == nil else { return } + guard featureFlagsProvider.isEnabled(.microsurvey), microsurvey == nil else { return } store.dispatch( MicrosurveyPromptAction(windowUUID: windowUUID, actionType: MicrosurveyPromptActionType.showPrompt) @@ -2069,14 +2067,21 @@ class BrowserViewController: UIViewController, private func removeAutoTranslatePrompt() { guard let prompt = autoTranslatePrompt else { return } + prompt.removeFromSuperview() + autoTranslatePrompt = nil + } + + private func updateAutoTranslatePromptConstraints() { + guard let prompt = autoTranslatePrompt else { return } + prompt.removeFromSuperview() if isBottomSearchBar { - overKeyboardContainer.removeArrangedView(prompt) + overKeyboardContainer.addArrangedViewToTop(prompt, animated: false, completion: nil) } else { - bottomContainer.removeArrangedView(prompt) + bottomContainer.addArrangedViewToTop(prompt, animated: false, completion: nil) } - autoTranslatePrompt = nil + prompt.applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) } // MARK: - Native Error Page @@ -2663,6 +2668,14 @@ class BrowserViewController: UIViewController, case _ where state.navigationDestination != nil: guard let destination = state.navigationDestination else { return } handleNavigation(to: destination) + // clear the navigation state for BrowserViewControllerState to make diffing works for subsequent navigations. + store.dispatch( + NavigationBrowserAction( + navigationDestination: destination, + windowUUID: windowUUID, + actionType: NavigationBrowserActionType.navigationDestinationHandled + ) + ) default: break } } @@ -2741,6 +2754,8 @@ class BrowserViewController: UIViewController, showZeroSearchView() case .shortcutsLibrary: navigationHandler?.showShortcutsLibrary() + case .quickAnswers: + navigationHandler?.showQuickAnswers() case .privacyNoticeLink(let url): navigationHandler?.showPrivacyNoticeLink(url: url) case .certificatesFromErrorPage: @@ -2936,21 +2951,7 @@ class BrowserViewController: UIViewController, if data.isTranslated, let langCode = data.translatedToLanguage { configureShowOriginalHeader(for: alert, languageCode: langCode) } else { - let title: String = .Translations.LanguagePicker.Title - let attributedTitleKey = "attributedTitle" - alert.title = title - alert.setValue( - NSAttributedString( - string: title, - attributes: [ - .font: DefaultDynamicFontHelper.preferredBoldFont( - withTextStyle: .headline, - size: UIFont.labelFontSize - ) - ] - ), - forKey: attributedTitleKey - ) + alert.title = .Translations.LanguagePicker.Title } data.languages.forEach { code in @@ -2984,6 +2985,22 @@ class BrowserViewController: UIViewController, alert.addAction(UIAlertAction(title: .CancelString, style: .cancel)) } + if let title = alert.title { + let attributedTitleKey = "attributedTitle" + alert.setValue( + NSAttributedString( + string: title, + attributes: [ + .font: DefaultDynamicFontHelper.preferredBoldFont( + withTextStyle: .headline, + size: UIFont.labelFontSize + ) + ] + ), + forKey: attributedTitleKey + ) + } + if let popover = alert.popoverPresentationController { if let sourceButton { popover.sourceView = sourceButton @@ -4607,12 +4624,6 @@ extension BrowserViewController: TabManagerDelegate { // back/forward buttons never to become enabled, etc. on tab restore after launch. [FXIOS-9785, FXIOS-9781] assert(selectedTab.webView != nil, "Setup will fail if the webView is not initialized for selectedTab") - if selectedTab.isDownloadingDocument() { - navigationHandler?.showDocumentLoading() - } else { - navigationHandler?.removeDocumentLoading() - } - // Remove the old accessibilityLabel only when the selected tab isn't the previous tab. // When the previous webview isn't visible anymore we ensure proper clean up for tests. if let previousWebView = previousTab?.webView, selectedTab != previousTab { @@ -4623,6 +4634,82 @@ extension BrowserViewController: TabManagerDelegate { previousWebView.removeFromSuperview() } + let needsReloadRefactorEnabled = featureFlagsProvider.isEnabled(.needsReloadRefactor) + var needsReload = false + if let webView = selectedTab.webView { + webView.accessibilityLabel = .WebViewAccessibilityLabel + webView.accessibilityIdentifier = AccessibilityIdentifiers.Browser.WebView.contentView + webView.accessibilityElementsHidden = false + + updateSelectedTabWebview(selectedTab: selectedTab, previousTab: previousTab, webView: webView) + + // FXIOS-14783: Experimentation on removing this code, do not add anything in there + if !needsReloadRefactorEnabled { + if selectedTab.isFxHomeTab { + // Added as initial fix for WKWebView memory leak. Needs further investigation. + // See: https://mozilla-hub.atlassian.net/browse/FXIOS-10612] + + // [https://mozilla-hub.atlassian.net/browse/FXIOS-10335] + needsReload = true + } + } + + // Do not reload if it's an about:blank page [FXIOS-14782] + if webView.url == nil && selectedTab.url?.absoluteString != "about:blank" { + logger.log("Webview was zombified, reloading tab upon selection", level: .debug, category: .lifecycle) + // [FXIOS-15440] Blank page when opening inactive tabs from tab tray + // The URL is nil when this happens so we reload that tab upon selection + needsReload = true + } + } + + updateUIAfterTabSelection(selectedTab: selectedTab, previousTab: previousTab) + + // FXIOS-14783: Experimentation on removing this code, do not add anything in there + /// If the selectedTab is showing an error page trigger a reload + if !needsReloadRefactorEnabled, + let url = selectedTab.url, + let internalUrl = InternalURL(url), + internalUrl.isErrorPage { + needsReload = true + } + + // FXIOS-14783: Experimentation on removing this code, do not add anything in there + if !needsReloadRefactorEnabled { + // Do not reload when it's an about:blank page or has a temporary document + if selectedTab.temporaryDocument != nil || selectedTab.url?.absoluteString == "about:blank" { + needsReload = false + } + } + + if needsReload { + selectedTab.reload() + } + } + + private func updateSelectedTabWebview(selectedTab: Tab, previousTab: Tab?, webView: TabWebView) { + if selectedTab.isFxHomeTab && previousTab != nil { + store.dispatch( + GeneralBrowserAction( + windowUUID: windowUUID, + actionType: GeneralBrowserActionType.didSelectedTabChangeToHomepage + ) + ) + } else if let url = webView.url, InternalURL(url)?.isErrorPage == true { + updateInContentHomePanel(url) + } + } + + // Selecting a new tab triggers a lot of UI updates, with some of them being to ensure the content + // shown is the right one for that particular tab content. + private func updateUIAfterTabSelection(selectedTab: Tab, previousTab: Tab?) { + // Ensure tab PDF content is up-to-date + if selectedTab.isDownloadingDocument() { + navigationHandler?.showDocumentLoading() + } else { + navigationHandler?.removeDocumentLoading() + } + + // Update theme upon tab selection if previousTab == nil || selectedTab.isPrivate != previousTab?.isPrivate { applyTheme() @@ -4655,40 +4742,6 @@ extension BrowserViewController: TabManagerDelegate { scrollController.tabProvider = TabProviderAdapter(selectedTab) } - var needsReload = false - if let webView = selectedTab.webView { - webView.accessibilityLabel = .WebViewAccessibilityLabel - webView.accessibilityIdentifier = "contentView" - webView.accessibilityElementsHidden = false - - if selectedTab.isFxHomeTab && previousTab != nil { - store.dispatch( - GeneralBrowserAction( - windowUUID: windowUUID, - actionType: GeneralBrowserActionType.didSelectedTabChangeToHomepage - ) - ) - } else if let url = webView.url, InternalURL(url)?.isErrorPage == true { - updateInContentHomePanel(url) - } - - // FXIOS-14783: Experimentation on removing this code, do not add anything in there - if !featureFlags.isFeatureEnabled(.needsReloadRefactor, checking: .buildOnly) { - if selectedTab.isFxHomeTab { - // Added as initial fix for WKWebView memory leak. Needs further investigation. - // See: https://mozilla-hub.atlassian.net/browse/FXIOS-10612] + - // [https://mozilla-hub.atlassian.net/browse/FXIOS-10335] - needsReload = true - } - - if webView.url == nil { - // The webView can go gray if it was zombified due to memory pressure. - // When this happens, the URL is nil, so try restoring the page upon selection. - needsReload = true - } - } - } - updateTabCountUsingTabManager(tabManager) bottomContentStackView.removeAllArrangedViews() @@ -4727,27 +4780,6 @@ extension BrowserViewController: TabManagerDelegate { } else if isSwipingTabsEnabled { addressToolbarContainer.updateSkeletonAddressBarsVisibility(tabManager: tabManager) } - - // FXIOS-14783: Experimentation on removing this code, do not add anything in there - /// If the selectedTab is showing an error page trigger a reload - if !featureFlags.isFeatureEnabled(.needsReloadRefactor, checking: .buildOnly), - let url = selectedTab.url, - let internalUrl = InternalURL(url), - internalUrl.isErrorPage { - needsReload = true - } - - // FXIOS-14783: Experimentation on removing this code, do not add anything in there - if !featureFlags.isFeatureEnabled(.needsReloadRefactor, checking: .buildOnly) { - // Do not reload when it's an about:blank page or has a temporary document - if selectedTab.temporaryDocument != nil || selectedTab.url?.absoluteString == "about:blank" { - needsReload = false - } - } - - if needsReload { - selectedTab.reload() - } } func tabManager(_ tabManager: TabManager, didAddTab tab: Tab, placeNextToParentTab: Bool, isRestoring: Bool) { @@ -5071,10 +5103,6 @@ extension BrowserViewController: TopTabsDelegate { func topTabsDidPressPrivateMode() { updateZoomPageBarVisibility(visible: false) } - - func topTabsShowCloseTabsToast() { - showLegacyCloseTabToast(message: .TabsTray.CloseTabsToast.SingleTabTitle) - } } extension BrowserViewController { diff --git a/firefox-ios/Client/Frontend/Browser/EnhancedTrackingProtection/SlideoverPresentationController.swift b/firefox-ios/Client/Frontend/Browser/EnhancedTrackingProtection/SlideoverPresentationController.swift index a7b0dd2ca187c..a0569b6e5e921 100644 --- a/firefox-ios/Client/Frontend/Browser/EnhancedTrackingProtection/SlideoverPresentationController.swift +++ b/firefox-ios/Client/Frontend/Browser/EnhancedTrackingProtection/SlideoverPresentationController.swift @@ -8,7 +8,7 @@ struct SlideOverUXConstants { static let ETPMenuCornerRadius: CGFloat = 8 } -class SlideOverPresentationController: UIPresentationController, LegacyFeatureFlaggable { +class SlideOverPresentationController: UIPresentationController, FeatureFlaggable { let blurEffectView: UIVisualEffectView var tapGestureRecognizer = UITapGestureRecognizer() var globalETPStatus: Bool @@ -89,8 +89,7 @@ class SlideOverPresentationController: UIPresentationController, LegacyFeatureFl @objc func dismissController() { - let trackingProtectionRefactorStatus = featureFlags.isFeatureEnabled(.trackingProtectionRefactor, - checking: .buildOnly) + let trackingProtectionRefactorStatus = featureFlagsProvider.isEnabled(.trackingProtectionRefactor) if trackingProtectionRefactorStatus { enhancedTrackingProtectionMenuDelegate?.didFinish() } else { diff --git a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift index c1f970f590087..515a482d1c948 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift @@ -7,7 +7,7 @@ import Foundation import MenuKit import Shared -struct MainMenuConfigurationUtility: Equatable, LegacyFeatureFlaggable { +struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { private struct Icons { static let findInPage = StandardImageIdentifiers.Large.search static let bookmarksTray = StandardImageIdentifiers.Large.bookmarkTray @@ -28,11 +28,11 @@ struct MainMenuConfigurationUtility: Equatable, LegacyFeatureFlaggable { } private var shouldShowReportSiteIssue: Bool { - featureFlags.isFeatureEnabled(.reportSiteIssue, checking: .buildOnly) + featureFlagsProvider.isEnabled(.reportSiteIssue) } private var isNewAppearanceMenuOn: Bool { - featureFlags.isFeatureEnabled(.appearanceMenu, checking: .buildOnly) + featureFlagsProvider.isEnabled(.appearanceMenu) } private var isSummarizerOn: Bool { @@ -412,7 +412,7 @@ struct MainMenuConfigurationUtility: Equatable, LegacyFeatureFlaggable { tabInfo: MainMenuTabInfo, localeProvider: LocaleProvider ) -> MenuElement? { - guard featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly), + guard featureFlagsProvider.isEnabled(.translationLanguagePicker), let translationConfig = tabInfo.translationConfiguration, translationConfig.isTranslationFeatureEnabled, translationConfig.state != nil diff --git a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift index 5dc62cf427a4f..04fc4bd9f0bde 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuCoordinator.swift @@ -178,8 +178,10 @@ class MainMenuCoordinator: BaseCoordinator, LegacyFeatureFlaggable { let manager = PreferredTranslationLanguagesManager(prefs: prefs) let supported = await ASTranslationModelsFetcher.shared.fetchSupportedTargetLanguages() let languages = manager.preferredLanguages(supportedTargetLanguages: supported) - let pageLanguage = try? await TranslationsService().detectPageLanguage(for: windowUUID) - let filteredLanguages = languages.filter { $0 != pageLanguage } + let pageLanguage = isTranslated + ? translationConfig?.sourceLanguage + : (try? await TranslationsService().detectPageLanguage(for: windowUUID)) + let filteredLanguages = languages.filter { $0 != pageLanguage && $0 != translatedLanguage } if isSingleLanguageFlow, let language = filteredLanguages.first, !isTranslated { store.dispatch(TranslationLanguageSelectedAction( windowUUID: windowUUID, diff --git a/firefox-ios/Client/Frontend/Browser/RelayController.swift b/firefox-ios/Client/Frontend/Browser/RelayController.swift index 4685c42ccc209..bb5253086a69e 100644 --- a/firefox-ios/Client/Frontend/Browser/RelayController.swift +++ b/firefox-ios/Client/Frontend/Browser/RelayController.swift @@ -53,7 +53,7 @@ final class RelayController: RelayControllerProtocol, Notifiable { #if targetEnvironment(simulator) && MOZ_CHANNEL_developer return true #else - return LegacyFeatureFlagsManager.shared.isFeatureEnabled(.relayIntegration, checking: .buildOnly) + return (AppContainer.shared.resolve() as FeatureFlagProviding).isEnabled(.relayIntegration) #endif }() diff --git a/firefox-ios/Client/Frontend/Browser/Search/SearchViewController.swift b/firefox-ios/Client/Frontend/Browser/Search/SearchViewController.swift index 716d25d86a2a5..5c821eba124a8 100644 --- a/firefox-ios/Client/Frontend/Browser/Search/SearchViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Search/SearchViewController.swift @@ -37,7 +37,8 @@ protocol SearchViewControllerDelegate: AnyObject { class SearchViewController: SiteTableViewController, KeyboardHelperDelegate, SearchViewDelegate, - LegacyFeatureFlaggable, + FeatureFlaggable, + UserFeaturePreferenceProvider, Notifiable { typealias ExtraKey = TelemetryWrapper.EventExtraKey @@ -628,7 +629,7 @@ class SearchViewController: SiteTableViewController, } } case .firefoxSuggestions: - if featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) { + if featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled { let firefoxSuggestion = viewModel.firefoxSuggestions[indexPath.row] if searchTelemetry?.visibleFirefoxSuggestions .contains(where: { $0.url == firefoxSuggestion.url }) == false { diff --git a/firefox-ios/Client/Frontend/Browser/Search/SearchViewModel.swift b/firefox-ios/Client/Frontend/Browser/Search/SearchViewModel.swift index 523de3773974f..51c0a0ff4f985 100644 --- a/firefox-ios/Client/Frontend/Browser/Search/SearchViewModel.swift +++ b/firefox-ios/Client/Frontend/Browser/Search/SearchViewModel.swift @@ -18,7 +18,9 @@ protocol SearchViewDelegate: AnyObject { } @MainActor -class SearchViewModel: LegacyFeatureFlaggable, LoaderListener { +class SearchViewModel: FeatureFlaggable, + UserFeaturePreferenceProvider, + LoaderListener { private var profile: Profile private var tabManager: TabManager private var suggestClient: SearchSuggestClient? @@ -188,7 +190,7 @@ class SearchViewModel: LegacyFeatureFlaggable, LoaderListener { // Show list of recent searches if user puts focus in the address bar but does not enter any text. @MainActor var shouldShowRecentSearches: Bool { - let isFeatureOn = featureFlags.isFeatureEnabled(.recentSearches, checking: .buildOnly) + let isFeatureOn = featureFlagsProvider.isEnabled(.recentSearches) let isSettingsToggleOn = model.shouldShowRecentSearches return isFeatureOn && isSettingsToggleOn && isZeroSearchState } @@ -196,7 +198,7 @@ class SearchViewModel: LegacyFeatureFlaggable, LoaderListener { // Show list of trending searches if user puts focus in the address bar but does not enter any text. @MainActor var shouldShowTrendingSearches: Bool { - let isFeatureOn = featureFlags.isFeatureEnabled(.trendingSearches, checking: .buildOnly) + let isFeatureOn = featureFlagsProvider.isEnabled(.trendingSearches) let isSettingsToggleOn = model.shouldShowTrendingSearches return isFeatureOn && isSettingsToggleOn && isZeroSearchState } @@ -301,7 +303,7 @@ class SearchViewModel: LegacyFeatureFlaggable, LoaderListener { let includeNonSponsored = shouldShowNonSponsoredSuggestions let includeSponsored = shouldShowSponsoredSuggestions - guard featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) + guard featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled && (includeNonSponsored || includeSponsored) else { if !firefoxSuggestions.isEmpty { firefoxSuggestions = [] diff --git a/firefox-ios/Client/Frontend/Browser/StartAtHome/StartAtHomeHelper.swift b/firefox-ios/Client/Frontend/Browser/StartAtHome/StartAtHomeHelper.swift index 177677818f8be..6c9ff1a02fbc6 100644 --- a/firefox-ios/Client/Frontend/Browser/StartAtHome/StartAtHomeHelper.swift +++ b/firefox-ios/Client/Frontend/Browser/StartAtHome/StartAtHomeHelper.swift @@ -5,7 +5,7 @@ import Shared import Common -class StartAtHomeHelper: LegacyFeatureFlaggable { +class StartAtHomeHelper: UserFeaturePreferenceProvider { private struct Constants { static let hoursToTriggerStartAtHome = 4 static let secondsToTriggerStartAtHome = 5 @@ -38,7 +38,7 @@ class StartAtHomeHelper: LegacyFeatureFlaggable { var startAtHomeSetting: StartAtHomeSetting { get { - let pref: StartAtHome = featureFlags.getCustomState(for: .startAtHome) ?? .afterFourHours + let pref = userPreferences.startAtHomeSetting return StartAtHomeSetting(rawValue: pref.rawValue) ?? .afterFourHours } set { prefs.setString(newValue.rawValue, forKey: PrefsKeys.FeatureFlags.StartAtHome) } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Action/RemoteTabsPanelAction.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Action/RemoteTabsPanelAction.swift index bf6eaf1f38bb1..9b45c191088e6 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Action/RemoteTabsPanelAction.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Action/RemoteTabsPanelAction.swift @@ -46,7 +46,6 @@ enum RemoteTabsPanelActionType: ActionType { case closeTabCompatible case openSelectedURL case closeSelectedRemoteURL - case undoCloseSelectedRemoteURL case flushTabCommands case remoteDevicesChanged } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift index ee398d332688a..58a4e8617ebf5 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Action/TabPanelAction.swift @@ -55,12 +55,10 @@ enum TabPanelViewActionType: ActionType { case tabPanelDidAppear case addNewTab case closeTab - case undoClose case closeAllTabs case cancelCloseAllTabs case confirmCloseAllTabs case deleteTabsOlderThan - case undoCloseAllTabs case moveTab case learnMorePrivateMode case selectTab diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/LayoutManager/TabsSectionManager.swift b/firefox-ios/Client/Frontend/Browser/Tabs/LayoutManager/TabsSectionManager.swift index aab5cfd2432de..eccf3e637a477 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/LayoutManager/TabsSectionManager.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/LayoutManager/TabsSectionManager.swift @@ -19,6 +19,8 @@ final class TabsSectionManager: LegacyFeatureFlaggable { static let iPadTopSiteInset: CGFloat = 25 static let verticalInset: CGFloat = 20 static let experimentTitleHeight: CGFloat = 20 + static let experimentCellPortraitAspectRatio: CGFloat = 1.4 + static let experimentCellLandscapeAspectRatio: CGFloat = 0.67 } static func leadingInset(traitCollection: UITraitCollection, @@ -68,6 +70,10 @@ final class TabsSectionManager: LegacyFeatureFlaggable { } func experimentLayoutSection(_ layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { + guard UIDevice.current.userInterfaceIdiom != .pad else { + return experimentLayoutSectionIpad(layoutEnvironment) + } + let availableWidth = layoutEnvironment.container.effectiveContentSize.width let maxNumberOfCellsPerRow = Int(availableWidth / UX.cellEstimatedWidth) let minNumberOfCellsPerRow = 2 @@ -78,7 +84,7 @@ final class TabsSectionManager: LegacyFeatureFlaggable { ? minNumberOfCellsPerRow : maxNumberOfCellsPerRow - let cellHeight: CGFloat = UX.cellAbsoluteHeight + let cellHeight = UX.cellAbsoluteHeight let itemWidth: CGFloat = 1.0 / CGFloat(numberOfCellsPerRow) let itemSize = NSCollectionLayoutSize( @@ -121,4 +127,73 @@ final class TabsSectionManager: LegacyFeatureFlaggable { return section } + + // MARK: - iPad experiment layout + + /// Uses aspect ratio math to derive cell height from actual cell width so that portrait + /// gets taller cells and landscape gets shorter cells + private func experimentLayoutSectionIpad( + _ layoutEnvironment: NSCollectionLayoutEnvironment + ) -> NSCollectionLayoutSection { + let availableWidth = layoutEnvironment.container.effectiveContentSize.width + let availableHeight = layoutEnvironment.container.effectiveContentSize.height + let maxNumberOfCellsPerRow = Int(availableWidth / UX.cellEstimatedWidth) + let minNumberOfCellsPerRow = 2 + + // maxNumberOfCellsPerRow returns 1 on smaller screen sizes which is inconvenient to scroll through + // so here we check we have 2 cells per row at minimum. + let numberOfCellsPerRow = maxNumberOfCellsPerRow < minNumberOfCellsPerRow + ? minNumberOfCellsPerRow + : maxNumberOfCellsPerRow + + let horizontalInset = TabsSectionManager.leadingInset(traitCollection: layoutEnvironment.traitCollection) + let totalSpacing = CGFloat(numberOfCellsPerRow - 1) * UX.cardSpacing + let totalHorizontalInset = horizontalInset * 2 + let usableWidth = availableWidth - totalHorizontalInset - totalSpacing + let cellWidth = usableWidth / CGFloat(numberOfCellsPerRow) + let aspectRatio = availableWidth > availableHeight + ? UX.experimentCellLandscapeAspectRatio + : UX.experimentCellPortraitAspectRatio + let cellHeight = (cellWidth * aspectRatio).rounded() + let itemWidth: CGFloat = 1.0 / CGFloat(numberOfCellsPerRow) + + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(itemWidth), + heightDimension: .absolute(cellHeight) + ) + + let titleSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(UX.experimentTitleHeight) + ) + + let titleSupplementary = NSCollectionLayoutSupplementaryItem( + layoutSize: titleSize, + elementKind: TabTitleSupplementaryView.cellIdentifier, + containerAnchor: NSCollectionLayoutAnchor(edges: [.bottom]) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize, supplementaryItems: [titleSupplementary]) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1), + heightDimension: .absolute(cellHeight) + ) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, + subitem: item, + count: numberOfCellsPerRow) + group.interItemSpacing = .fixed(UX.cardSpacing) + + let section = NSCollectionLayoutSection(group: group) + + let isAccessibilitySize = layoutEnvironment.traitCollection.preferredContentSizeCategory.isAccessibilityCategory + section.contentInsets = NSDirectionalEdgeInsets( + top: UX.verticalInset, + leading: horizontalInset, + bottom: isAccessibilitySize ? UX.experimentA11yCardSpacing : UX.experimentCardSpacing, + trailing: horizontalInset + ) + section.interGroupSpacing = isAccessibilitySize ? UX.experimentA11yCardSpacing : UX.experimentCardSpacing + + return section + } } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift index 961b9c26025ff..9d8c69ee5b448 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Middleware/TabManagerMiddleware.swift @@ -13,20 +13,18 @@ import SummarizeKit import enum MozillaAppServices.BookmarkRoots @MainActor -final class TabManagerMiddleware: LegacyFeatureFlaggable, - CanRemoveQuickActionBookmark { +final class TabManagerMiddleware: FeatureFlaggable, CanRemoveQuickActionBookmark { private let profile: Profile private let logger: Logger private let windowManager: WindowManager private let bookmarksSaver: BookmarksSaver - private let toastTelemetry: ToastTelemetry private let summarizerNimbusUtils: SummarizerNimbusUtils private let summarizerConfigFactory: SummarizerConfigFactory private let tabsPanelTelemetry: TabsPanelTelemetry var bookmarksHandler: BookmarksHandler private var isTabTrayUIExperimentsEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayUIExperiments) && UIDevice.current.userInterfaceIdiom != .pad } private var isSummarizerEnabled: Bool { @@ -54,7 +52,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, self.logger = logger self.windowManager = windowManager self.bookmarksSaver = bookmarksSaver ?? DefaultBookmarksSaver(profile: profile) - self.toastTelemetry = ToastTelemetry(gleanWrapper: gleanWrapper) self.tabsPanelTelemetry = TabsPanelTelemetry(gleanWrapper: gleanWrapper, logger: logger) } @@ -119,7 +116,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, copyURL(tabID: tabUUID, uuid: action.windowUUID) case TabPeekActionType.closeTab: - // TODO: verify if this works for closing a tab from an unselected tab panel guard let tabsState = state.componentState(TabsPanelState.self, for: .tabsPanel, window: action.windowUUID) else { return } @@ -139,9 +135,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, case RemoteTabsPanelActionType.closeSelectedRemoteURL: guard let url = action.url, let deviceId = action.targetDeviceId else { return } closeSelectedRemoteTab(deviceId: deviceId, url: url, windowUUID: action.windowUUID) - case RemoteTabsPanelActionType.undoCloseSelectedRemoteURL: - guard let url = action.url, let deviceId = action.targetDeviceId else { return } - undoCloseSelectedRemoteTab(deviceId: deviceId, url: url, windowUUID: action.windowUUID) case RemoteTabsPanelActionType.flushTabCommands: guard let deviceId = action.targetDeviceId else { return } flushTabCommands(deviceId: deviceId, windowUUID: action.windowUUID) @@ -226,9 +219,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, uuid: action.windowUUID, isPrivate: action.panelType == .privateTabs) - case TabPanelViewActionType.undoClose: - undoCloseTab(state: state, uuid: action.windowUUID) - case TabPanelViewActionType.cancelCloseAllTabs: tabsPanelTelemetry.closeAllTabsSheetOptionSelected( option: .cancel, @@ -242,9 +232,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, guard let period = action.deleteTabPeriod else { return } deleteNormalTabsOlderThan(period: period, uuid: action.windowUUID) - case TabPanelViewActionType.undoCloseAllTabs: - undoCloseAllTabs(uuid: action.windowUUID) - case TabPanelViewActionType.selectTab: guard let tabUUID = action.tabUUID else { return } selectTab( @@ -307,10 +294,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, self.profile.addTabToCommandQueue(deviceId, url: url) } - private func undoCloseSelectedRemoteTab(deviceId: String, url: URL, windowUUID: WindowUUID) { - self.profile.removeTabFromCommandQueue(deviceId, url: url) - } - private func flushTabCommands(deviceId: String, windowUUID: WindowUUID) { self.profile.flushTabCommands(toDeviceId: deviceId) } @@ -441,7 +424,7 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, /// Close tab and trigger refresh /// - Parameter tabUUID: UUID of the tab to be closed/removed private func closeTabFromTabPanel(with tabUUID: TabUUID, uuid: WindowUUID, isPrivate: Bool) { - let shouldDismiss = self.closeTab(with: tabUUID, uuid: uuid, isPrivate: isPrivate) + let shouldDismiss = closeTab(with: tabUUID, uuid: uuid, isPrivate: isPrivate) triggerRefresh(uuid: uuid, isPrivate: isPrivate) if isPrivate && tabManager(for: uuid).privateTabs.isEmpty { @@ -484,34 +467,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, store.dispatch(action) } - /// Handles undoing the close tab action, gets the backup tab from `TabManager` - private func undoCloseTab(state: AppState, uuid: WindowUUID) { - toastTelemetry.undoClosedSingleTab() - let tabManager = tabManager(for: uuid) - guard tabManager.backupCloseTab != nil else { return } - - tabManager.undoCloseTab() - - guard let tabsState = state.componentState(TabsPanelState.self, for: .tabsPanel, window: uuid) else { return } - - let model = getTabsDisplayModel(for: tabsState.isPrivateMode, uuid: uuid) - let refreshAction = TabPanelMiddlewareAction(tabDisplayModel: model, - windowUUID: uuid, - actionType: TabPanelMiddlewareActionType.refreshTabs) - store.dispatch(refreshAction) - - // Scroll to the restored tab so the user knows it was restored, especially if it was restored off screen - // (e.g. restoring the tab in the last row, first column) - let scrollBehavior: TabScrollBehavior = tabManager.backupCloseTab != nil - ? .scrollToTab(withTabUUID: tabManager.backupCloseTab!.tab.tabUUID, shouldAnimate: true) - : .scrollToSelectedTab(shouldAnimate: true) - let scrollAction = TabPanelMiddlewareAction(tabDisplayModel: model, - scrollBehavior: scrollBehavior, - windowUUID: uuid, - actionType: TabPanelMiddlewareActionType.scrollToTab) - store.dispatch(scrollAction) - } - private func closeAllTabs(state: AppState, uuid: WindowUUID) { let tabManager = tabManager(for: uuid) guard let tabsState = state.componentState(TabsPanelState.self, for: .tabsPanel, window: uuid) else { return } @@ -553,26 +508,6 @@ final class TabManagerMiddleware: LegacyFeatureFlaggable, } } - private func undoCloseAllTabs(uuid: WindowUUID) { - toastTelemetry.undoClosedAllTabs() - let tabManager = tabManager(for: uuid) - tabManager.undoCloseAllTabs() - - // The private tab panel is the only panel that stays open after a close all tabs action - let model = getTabsDisplayModel(for: true, uuid: uuid) - let refreshAction = TabPanelMiddlewareAction(tabDisplayModel: model, - windowUUID: uuid, - actionType: TabPanelMiddlewareActionType.refreshTabs) - store.dispatch(refreshAction) - - // Scroll to the selected tab if all closed tabs are restored - let scrollAction = TabPanelMiddlewareAction(tabDisplayModel: model, - scrollBehavior: .scrollToSelectedTab(shouldAnimate: true), - windowUUID: uuid, - actionType: TabPanelMiddlewareActionType.scrollToTab) - store.dispatch(scrollAction) - } - private func didTapLearnMoreAboutPrivate(with urlRequest: URLRequest, uuid: WindowUUID) { addNewTab(with: urlRequest, isPrivate: true, showOverlay: false, for: uuid) } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/State/RemoteTabsPanelState.swift b/firefox-ios/Client/Frontend/Browser/Tabs/State/RemoteTabsPanelState.swift index 7bed914aebf68..7bfed3a763ed6 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/State/RemoteTabsPanelState.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/State/RemoteTabsPanelState.swift @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Common +import CopyWithUpdates import Redux import Storage @@ -48,12 +49,13 @@ enum RemoteTabsPanelEmptyStateReason { } /// State for RemoteTabsPanel. WIP. +@CopyWithUpdates struct RemoteTabsPanelState: ScreenState, Sendable { + let windowUUID: WindowUUID let refreshState: RemoteTabsPanelRefreshState let allowsRefresh: Bool let clientAndTabs: [ClientAndTabs] let showingEmptyState: RemoteTabsPanelEmptyStateReason?// If showing empty (or error) state - let windowUUID: WindowUUID let devices: [Device] init(appState: AppState, uuid: WindowUUID) { @@ -66,12 +68,7 @@ struct RemoteTabsPanelState: ScreenState, Sendable { return } - self.init(windowUUID: panelState.windowUUID, - refreshState: panelState.refreshState, - allowsRefresh: panelState.allowsRefresh, - clientAndTabs: panelState.clientAndTabs, - showingEmptyState: panelState.showingEmptyState, - devices: panelState.devices) + self = panelState.copyWithUpdates() } init(windowUUID: WindowUUID) { @@ -126,61 +123,39 @@ struct RemoteTabsPanelState: ScreenState, Sendable { } private static func handleRefreshDidBeginAction(state: RemoteTabsPanelState) -> RemoteTabsPanelState { - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: .refreshing, - allowsRefresh: state.allowsRefresh, - clientAndTabs: state.clientAndTabs, - showingEmptyState: state.showingEmptyState, - devices: state.devices) + return state.copyWithUpdates(refreshState: .refreshing) } private static func handleRefreshDidFailAction(reason: RemoteTabsPanelEmptyStateReason, state: RemoteTabsPanelState) -> RemoteTabsPanelState { let allowsRefresh = reason.allowsRefresh - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: .idle, - allowsRefresh: allowsRefresh, - clientAndTabs: state.clientAndTabs, - showingEmptyState: reason, - devices: state.devices) + return state.copyWithUpdates(refreshState: .idle, + allowsRefresh: allowsRefresh, + showingEmptyState: reason) } private static func handleRefreshDidSucceedAction(clientAndTabs: [ClientAndTabs], state: RemoteTabsPanelState, action: RemoteTabsPanelAction) -> RemoteTabsPanelState { - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: .idle, - allowsRefresh: true, - clientAndTabs: clientAndTabs, - showingEmptyState: nil, - devices: action.devices ?? state.devices) + return state.copyWithUpdates(refreshState: .idle, + allowsRefresh: true, + clientAndTabs: clientAndTabs, + showingEmptyState: nil, + devices: action.devices ?? state.devices) } private static func handleRemoteDevicesChangedAction(devices: [Device], state: RemoteTabsPanelState) -> RemoteTabsPanelState { - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: .idle, - allowsRefresh: state.allowsRefresh, - clientAndTabs: state.clientAndTabs, - showingEmptyState: state.showingEmptyState, - devices: devices) + return state.copyWithUpdates(refreshState: .idle, + devices: devices) } private static func handleSyncDidBeginAction(state: RemoteTabsPanelState) -> RemoteTabsPanelState { - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: .syncingTabs, - allowsRefresh: false, - clientAndTabs: state.clientAndTabs, - showingEmptyState: state.showingEmptyState, - devices: state.devices) + return state.copyWithUpdates(refreshState: .syncingTabs, + allowsRefresh: false) } static func defaultState(from state: RemoteTabsPanelState) -> RemoteTabsPanelState { - return RemoteTabsPanelState(windowUUID: state.windowUUID, - refreshState: state.refreshState, - allowsRefresh: state.allowsRefresh, - clientAndTabs: state.clientAndTabs, - showingEmptyState: state.showingEmptyState, - devices: state.devices) + return state.copyWithUpdates() } } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/State/TabPeekState.swift b/firefox-ios/Client/Frontend/Browser/Tabs/State/TabPeekState.swift index 7a1f7211f4b5d..31ea146d72464 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/State/TabPeekState.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/State/TabPeekState.swift @@ -4,8 +4,11 @@ import Redux import Common +import CopyWithUpdates +@CopyWithUpdates struct TabPeekState: ScreenState { + let windowUUID: WindowUUID let showAddToBookmarks: Bool let showRemoveBookmark: Bool let showSendToDevice: Bool @@ -13,7 +16,6 @@ struct TabPeekState: ScreenState { let showCloseTab: Bool let previewAccessibilityLabel: String let screenshot: UIImage - let windowUUID: WindowUUID init(appState: AppState, uuid: WindowUUID) { guard let tabPeekState = appState.componentState( @@ -25,14 +27,7 @@ struct TabPeekState: ScreenState { return } - self.init(windowUUID: tabPeekState.windowUUID, - showAddToBookmarks: tabPeekState.showAddToBookmarks, - showRemoveBookmark: tabPeekState.showRemoveBookmark, - showSendToDevice: tabPeekState.showSendToDevice, - showCopyURL: tabPeekState.showCopyURL, - showCloseTab: tabPeekState.showCloseTab, - previewAccessibilityLabel: tabPeekState.previewAccessibilityLabel, - screenshot: tabPeekState.screenshot) + self = tabPeekState.copyWithUpdates() } init(windowUUID: WindowUUID, @@ -62,7 +57,7 @@ struct TabPeekState: ScreenState { switch action.actionType { case TabPeekActionType.loadTabPeek: guard let tabPeekModel = action.tabPeekModel else { return state } - return TabPeekState(windowUUID: state.windowUUID, + return state.copyWithUpdates( showAddToBookmarks: tabPeekModel.canTabBeSaved, showRemoveBookmark: tabPeekModel.canTabBeRemoved, showSendToDevice: tabPeekModel.isSyncEnabled && tabPeekModel.canTabBeSaved, @@ -75,6 +70,6 @@ struct TabPeekState: ScreenState { } static func defaultState(from state: TabPeekState) -> TabPeekState { - return TabPeekState(windowUUID: state.windowUUID) + return state.copyWithUpdates() } } diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/TabTrayUtils.swift b/firefox-ios/Client/Frontend/Browser/Tabs/TabTrayUtils.swift index c3cf7dec9ee9a..3434bdd1c741a 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/TabTrayUtils.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/TabTrayUtils.swift @@ -25,7 +25,7 @@ protocol TabTrayUtils { /// Tiny utility to simplify checking for availability of the tab tray features @MainActor -struct DefaultTabTrayUtils: LegacyFeatureFlaggable, TabTrayUtils { +struct DefaultTabTrayUtils: FeatureFlaggable, TabTrayUtils { private enum UX { static let backgroundAlphaForBlur: CGFloat = 0.85 static let segmentedControlHeight: CGFloat = 53 @@ -33,15 +33,15 @@ struct DefaultTabTrayUtils: LegacyFeatureFlaggable, TabTrayUtils { } var isTabTrayUIExperimentsEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayUIExperiments) } var isTabTrayIpadUIExperimentsEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayiPadUIExperiments, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayiPadUIExperiments) } var isTabTrayTranslucencyEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayTranslucency, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayTranslucency) } var isReduceTransparencyEnabled: Bool { diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabAnimation.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabAnimation.swift index 1887a90c833e0..ee063dcfd66f4 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabAnimation.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/Animation/TabAnimation.swift @@ -322,9 +322,17 @@ extension TabTrayViewController: BasicAnimationControllerDelegate { context.containerView.addSubview(toView) + let isIpad = browserVC.traitCollection.userInterfaceIdiom == .pad && + browserVC.traitCollection.horizontalSizeClass == .regular + let shouldCropUsingContentContainerFrame = UIWindow.isPortrait && !isIpad + // Trigger animation async to be non blocking and allow UI to render DispatchQueue.main.async { - let tabSnapshot = self.buildTabSnapshot(selectedTab: selectedTab, contentContainer: contentContainer) + let tabSnapshot = self.buildTabSnapshot( + selectedTab: selectedTab, + contentContainer: contentContainer, + shouldCropUsingContentContainerFrame: shouldCropUsingContentContainerFrame + ) context.containerView.addSubview(tabSnapshot) var tabCell: ExperimentTabCell? @@ -359,19 +367,36 @@ extension TabTrayViewController: BasicAnimationControllerDelegate { } } - private func buildTabSnapshot(selectedTab: Tab, contentContainer: UIView) -> UIView { + private func buildTabSnapshot( + selectedTab: Tab, + contentContainer: UIView, + shouldCropUsingContentContainerFrame: Bool + ) -> UIView { let tabSnapshot = UIImageView(image: selectedTab.screenshot) - // crop the tab screenshot to the contentContainer frame so the animation - // and the initial transform doesn't stutter - if let image = tabSnapshot.image, let croppedImage = image.cgImage?.cropping( - to: CGRect( - x: contentContainer.frame.origin.x * image.scale, - y: contentContainer.frame.origin.y * image.scale, - width: contentContainer.frame.width * image.scale, - height: contentContainer.frame.height * image.scale - ) - ) { - tabSnapshot.image = UIImage(cgImage: croppedImage) + + // ScreenshotHelper applies contentContainer bounds only on iPhone portrait. + // Otherwise, the stored tab screenshot is based on the webView bounds. + if let image = tabSnapshot.image { + let cropRect: CGRect + if shouldCropUsingContentContainerFrame { + cropRect = CGRect( + x: contentContainer.frame.origin.x * image.scale, + y: contentContainer.frame.origin.y * image.scale, + width: contentContainer.frame.width * image.scale, + height: contentContainer.frame.height * image.scale + ) + } else { + cropRect = CGRect( + x: 0, + y: 0, + width: contentContainer.bounds.width * image.scale, + height: contentContainer.bounds.height * image.scale + ) + } + + if let croppedImage = image.cgImage?.cropping(to: cropRect) { + tabSnapshot.image = UIImage(cgImage: croppedImage) + } } tabSnapshot.clipsToBounds = true diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/ExperimentTabCell.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/ExperimentTabCell.swift index fc44c129b50a0..f04851d156ca6 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/ExperimentTabCell.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/ExperimentTabCell.swift @@ -27,7 +27,6 @@ final class ExperimentTabCell: UICollectionViewCell, ThemeApplicable, ReusableCe static let shadowRadius: CGFloat = 4 static let shadowOffset = CGSize(width: 0, height: 2) static let shadowOpacity: Float = 1 - static let thumbnailScreenshotHeight: CGFloat = 200 static let borderLayerName = "externalBorder" } // MARK: - Properties @@ -87,16 +86,25 @@ final class ExperimentTabCell: UICollectionViewCell, ThemeApplicable, ReusableCe closeButtonBlurView.addBlurEffectWithClearBackgroundAndClipping(using: .systemUltraThinMaterialDark) closeButtonBlurView.layer.cornerRadius = closeButtonBlurView.frame.height / 2 + + // Handles initial draw and non-rotation layout changes (e.g. multitasking resize). + guard isSelectedTab, backgroundHolder.bounds != borderLayer.frame else { return } + redrawExternalBorder() } override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) - guard isSelectedTab else { return } + // Defers redraw via Task so the border updates after the rotation animation completes. + guard isSelectedTab, + previousTraitCollection?.horizontalSizeClass != traitCollection.horizontalSizeClass + || previousTraitCollection?.verticalSizeClass != traitCollection.verticalSizeClass + else { return } + Task { await MainActor.run { [weak self] in guard let self else { return } - redrawExternalBorder() + self.redrawExternalBorder() } } } @@ -309,6 +317,8 @@ final class ExperimentTabCell: UICollectionViewCell, ThemeApplicable, ReusableCe override func prepareForReuse() { // Reset any close animations. super.prepareForReuse() + tabModel = nil + accessibilityLabel = nil screenshotView.image = nil smallFaviconView.isHidden = true removeExternalBorder(from: backgroundHolder) @@ -326,7 +336,6 @@ final class ExperimentTabCell: UICollectionViewCell, ThemeApplicable, ReusableCe backgroundHolder.topAnchor.constraint(equalTo: contentView.topAnchor), backgroundHolder.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), backgroundHolder.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), - backgroundHolder.heightAnchor.constraint(equalToConstant: UX.thumbnailScreenshotHeight), backgroundHolder.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), closeButton.heightAnchor.constraint(equalToConstant: UX.closeButtonHitTarget), diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsPanel.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsPanel.swift index 879986dc39d91..57b021ae22892 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsPanel.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsPanel.swift @@ -26,7 +26,7 @@ class RemoteTabsPanel: UIViewController, RemoteTabsClientAndTabsDataSourceDelegate, RemoteTabsEmptyViewDelegate, StoreSubscriber, - LegacyFeatureFlaggable, + FeatureFlaggable, TabTrayThemeable, Notifiable { typealias SubscriberStateType = RemoteTabsPanelState @@ -42,7 +42,7 @@ class RemoteTabsPanel: UIViewController, var notificationCenter: NotificationProtocol private let windowUUID: WindowUUID private var isTabTrayUIExperimentsEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayUIExperiments) && UIDevice.current.userInterfaceIdiom != .pad } @@ -232,10 +232,6 @@ class RemoteTabsPanel: UIViewController, handleCloseRemoteTab(deviceId, url: url) } - func remoteTabsClientAndTabsDataSourceDidUndo(deviceId: String, url: URL) { - handleUndoCloseTab(deviceId, url: url) - } - func remoteTabsClientAndTabsDataSourceDidTabCommandsFlush(deviceId: String) { handleTabCommandsFlush(deviceId) } @@ -271,16 +267,6 @@ class RemoteTabsPanel: UIViewController, refreshTabs(useCache: true) } - private func handleUndoCloseTab(_ deviceId: String, url: URL) { - let action = RemoteTabsPanelAction(url: url, - targetDeviceId: deviceId, - windowUUID: windowUUID, - actionType: RemoteTabsPanelActionType.undoCloseSelectedRemoteURL) - store.dispatch(action) - - refreshTabs(useCache: true) - } - private func handleTabCommandsFlush(_ deviceId: String) { let action = RemoteTabsPanelAction(targetDeviceId: deviceId, windowUUID: windowUUID, diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsViewController.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsViewController.swift index fcf31248b3d7b..b108dc05871a4 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/RemoteTabsViewController.swift @@ -17,7 +17,7 @@ class RemoteTabsViewController: UIViewController, Themeable, CollapsibleTableViewSection, LibraryPanelContextMenu, - LegacyFeatureFlaggable, + FeatureFlaggable, UITableViewDelegate, UITableViewDataSource { struct UX { @@ -31,7 +31,7 @@ class RemoteTabsViewController: UIViewController, private let logger: Logger private var isTabTrayUIExperimentsEnabled: Bool { - return featureFlags.isFeatureEnabled(.tabTrayUIExperiments, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.tabTrayUIExperiments) && UIDevice.current.userInterfaceIdiom != .pad } @@ -56,8 +56,6 @@ class RemoteTabsViewController: UIViewController, } }() - private var closeTabRemoteDeviceId: String? - private var closeTab: RemoteTab? private var tabCommandsFlushTimer: Timer? private let tabCommandsFlushDelay = 6.0 @@ -382,24 +380,6 @@ class RemoteTabsViewController: UIViewController, } let tab = clientAndTabs.tabs[indexPath.item] - // Setting the two private variables below so that the toast button action function has access to them - // since that function cannot have any parameters. - self.closeTabRemoteDeviceId = fxaDeviceId - self.closeTab = tab - - // Creating a modal with an undo button that will allow the user to undo closing the last remote tab - // they attempted to close - let viewModel = ButtonToastViewModel(labelText: .TabsTray.CloseTabsToast.SingleTabTitle, - buttonText: .UndoString) - let toast = ButtonToast(viewModel: viewModel, - theme: retrieveTheme(), - completion: { didTapUndoButton in - if didTapUndoButton { - self.undo() - } - }) - show(toast: toast) - self.remoteTabsPanel?.remoteTabsClientAndTabsDataSourceDidCloseURL(deviceId: fxaDeviceId, url: tab.URL) // Initiating the process of sending (i.e. executing) any unsent commands @@ -469,18 +449,6 @@ class RemoteTabsViewController: UIViewController, return headerView } - private func undo() { - guard let tabUrl = self.closeTab?.URL, let deviceId = self.closeTabRemoteDeviceId else { - return - } - - // Removing the close tab command from the command queue - remoteTabsPanel?.remoteTabsClientAndTabsDataSourceDidUndo(deviceId: deviceId, url: tabUrl) - - // Initiating the process of sending any unsent commands - self.flushTabCommands(deviceId: deviceId) - } - private func flushTabCommands(deviceId: String) { // If the timer property is set and is valid, we reset it. This will prevent flush // from being executed too often. It will run `self.tabCommandsFlushDelay` seconds diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabCell.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabCell.swift index 9f5b5dba5e498..5ddfc76530b06 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabCell.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabCell.swift @@ -236,6 +236,8 @@ final class TabCell: UICollectionViewCell, override func prepareForReuse() { // Reset any close animations. super.prepareForReuse() + tabModel = nil + accessibilityLabel = nil screenshotView.image = nil backgroundHolder.transform = .identity backgroundHolder.alpha = 1 diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabPeekViewController.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabPeekViewController.swift index e3072ab4e04b5..cb80e3f28c514 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabPeekViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabPeekViewController.swift @@ -8,7 +8,7 @@ import WebKit import Redux final class TabPeekViewController: UIViewController, - StoreSubscriber { + StoreSubscriber { typealias SubscriberStateType = TabPeekState var tabPeekState: TabPeekState diff --git a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabTrayViewController.swift b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabTrayViewController.swift index e364207d812c9..3bc0b4435bf68 100644 --- a/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabTrayViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/Tabs/Views/TabTrayViewController.swift @@ -40,6 +40,7 @@ final class TabTrayViewController: UIViewController, private struct UX { struct NavigationMenu { static let width: CGFloat = 343 + static let iPadWidth: CGFloat = 500 } static let fixedSpaceWidth: CGFloat = 32 @@ -345,7 +346,7 @@ final class TabTrayViewController: UIViewController, navigationItem.rightBarButtonItems = [doneButton] } case .regular: - navigationItem.titleView = segmentedControl + navigationItem.titleView = tabTrayUtils.shouldDisplayExperimentUI() ? experimentSegmentControl : segmentedControl } updateToolbarItems() } @@ -620,6 +621,11 @@ final class TabTrayViewController: UIViewController, } private func setupForiPad() { + guard !tabTrayUtils.shouldDisplayExperimentUI() else { + setupForiPadExperiment() + return + } + navigationItem.titleView = segmentedControl view.addSubviews(containerView) setupBlurView() @@ -640,6 +646,30 @@ final class TabTrayViewController: UIViewController, ]) } + private func setupForiPadExperiment() { + navigationItem.titleView = experimentSegmentControl + view.addSubviews(containerView) + containerView.addSubview(panelContainer) + setupBlurView() + + NSLayoutConstraint.activate([ + containerView.topAnchor.constraint(equalTo: view.topAnchor), + containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + + panelContainer.topAnchor.constraint(equalTo: containerView.topAnchor), + panelContainer.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + panelContainer.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + panelContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + + // Because experimentSegmentControl doesn't inherit from UISegmentControl + // we need to set height and width constraints + experimentSegmentControl.widthAnchor.constraint(equalToConstant: UX.NavigationMenu.iPadWidth), + experimentSegmentControl.heightAnchor.constraint(equalToConstant: tabTrayUtils.segmentedControlHeight) + ]) + } + private func updateToolbarItems() { // iPad configuration guard !isRegularLayout else { diff --git a/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseCoordinator.swift b/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseCoordinator.swift index dda964fb4d943..13bf9e2b34140 100644 --- a/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseCoordinator.swift +++ b/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseCoordinator.swift @@ -22,7 +22,7 @@ protocol TermsOfUseCoordinatorDelegate: AnyObject { } @MainActor -final class TermsOfUseCoordinator: BaseCoordinator, TermsOfUseCoordinatorDelegate, LegacyFeatureFlaggable { +final class TermsOfUseCoordinator: BaseCoordinator, TermsOfUseCoordinatorDelegate, FeatureFlaggable { /// Prevents deep link route handling from dismissing the Terms of Use sheet override var isDismissible: Bool { false } @@ -110,7 +110,7 @@ final class TermsOfUseCoordinator: BaseCoordinator, TermsOfUseCoordinatorDelegat func shouldShowTermsOfUse(context: TriggerContext = .appLaunch) -> Bool { // 1. Feature must be enabled - guard featureFlags.isFeatureEnabled(.touFeature, checking: .buildOnly) else { return false } + guard featureFlagsProvider.isEnabled(.touFeature) else { return false } // 2. If user has already accepted, never show again let hasAcceptedTermsOfUse = prefs.boolForKey(PrefsKeys.TermsOfUseAccepted) ?? false diff --git a/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseState.swift b/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseState.swift index 0c1600f6341fb..ab8f34aa71b41 100644 --- a/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseState.swift +++ b/firefox-ios/Client/Frontend/Browser/TermsOfUse/TermsOfUseState.swift @@ -3,8 +3,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Common +import CopyWithUpdates import Redux +@CopyWithUpdates struct TermsOfUseState: ScreenState { let windowUUID: WindowUUID var hasAccepted: Bool @@ -20,10 +22,7 @@ struct TermsOfUseState: ScreenState { return } - self.init(windowUUID: termsOfUseState.windowUUID, - hasAccepted: termsOfUseState.hasAccepted, - wasDismissed: termsOfUseState.wasDismissed - ) + self = termsOfUseState.copyWithUpdates() } init(windowUUID: WindowUUID) { @@ -43,9 +42,7 @@ struct TermsOfUseState: ScreenState { } static func defaultState(from state: TermsOfUseState) -> TermsOfUseState { - return TermsOfUseState(windowUUID: state.windowUUID, - hasAccepted: state.hasAccepted, - wasDismissed: state.wasDismissed) + return state.copyWithUpdates() } static let reducer: Reducer = { state, action in @@ -74,24 +71,21 @@ struct TermsOfUseState: ScreenState { switch type { case .termsShown: - return TermsOfUseState(windowUUID: state.windowUUID, + return state.copyWithUpdates( hasAccepted: false, wasDismissed: false) case .termsAccepted: - return TermsOfUseState(windowUUID: state.windowUUID, + return state.copyWithUpdates( hasAccepted: true, wasDismissed: false) case .gestureDismiss, .remindMeLaterTapped: - return TermsOfUseState(windowUUID: state.windowUUID, - hasAccepted: state.hasAccepted, + return state.copyWithUpdates( wasDismissed: true) case .learnMoreLinkTapped, .privacyLinkTapped, .termsLinkTapped: - return TermsOfUseState(windowUUID: state.windowUUID, - hasAccepted: state.hasAccepted, - wasDismissed: state.wasDismissed) + return state.copyWithUpdates() } } } diff --git a/firefox-ios/Client/Frontend/Browser/ToastType.swift b/firefox-ios/Client/Frontend/Browser/ToastType.swift index c0079351cf558..4a1f138478eb7 100644 --- a/firefox-ios/Client/Frontend/Browser/ToastType.swift +++ b/firefox-ios/Client/Frontend/Browser/ToastType.swift @@ -10,8 +10,6 @@ enum ToastType: Equatable { case addBookmark(urlString: String) case addToReadingList case clearCookies - case closedSingleTab - case closedAllTabs(count: Int) case openNewTab case removeFromReadingList case removeShortcut @@ -26,12 +24,6 @@ enum ToastType: Equatable { return .LegacyAppMenu.AddToReadingListConfirmMessage case .clearCookies: return .Menu.EnhancedTrackingProtection.clearDataToastMessage - case .closedSingleTab: - return .TabsTray.CloseTabsToast.SingleTabTitle - case let .closedAllTabs(count: tabsCount): - return String.localizedStringWithFormat( - .TabsTray.CloseTabsToast.Title, - tabsCount) case .openNewTab: return .ContextMenuButtonToastNewTabOpenedLabelText case .removeFromReadingList: @@ -58,10 +50,6 @@ enum ToastType: Equatable { func reduxAction(for uuid: WindowUUID) -> Action? { switch self { - case .closedSingleTab: - return tabPanelAction(for: TabPanelViewActionType.undoClose, uuid: uuid) - case .closedAllTabs: - return tabPanelAction(for: TabPanelViewActionType.undoCloseAllTabs, uuid: uuid) case .retryTranslatingPage: return TranslationsAction(windowUUID: uuid, actionType: TranslationsActionType.didTapRetryFailedTranslation) case .clearCookies, diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/AddressToolbarContainer.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/AddressToolbarContainer.swift index 52ca8b4fb45cd..aa911562a09fc 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/AddressToolbarContainer.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/AddressToolbarContainer.swift @@ -56,7 +56,7 @@ final class AddressToolbarContainer: UIView, AddressToolbarDelegate, Autocompletable, URLBarViewProtocol, - LegacyFeatureFlaggable, + FeatureFlaggable, PrivateModeUI { private enum UX { static let toolbarHorizontalPadding: CGFloat = 16 @@ -568,8 +568,8 @@ final class AddressToolbarContainer: UIView, // We want to show suggestions if we turn on the trending searches or recent searches // which displays the zero search state. Only if not in private mode. - let isTrendingSearchEnabled = featureFlags.isFeatureEnabled(.trendingSearches, checking: .buildOnly) - let isRecentSearchEnabled = featureFlags.isFeatureEnabled(.recentSearches, checking: .buildOnly) + let isTrendingSearchEnabled = featureFlagsProvider.isEnabled(.trendingSearches) + let isRecentSearchEnabled = featureFlagsProvider.isEnabled(.recentSearches) let isRecentOrTrendingSearchEnabled = isTrendingSearchEnabled || isRecentSearchEnabled let isPrivateMode = model?.isPrivateMode ?? false let isZeroSearchEnabled = isRecentOrTrendingSearchEnabled && !isPrivateMode diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/AddressBarState.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/AddressBarState.swift index ac110fbe51749..17ee8b0c33ce1 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/AddressBarState.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/AddressBarState.swift @@ -3,10 +3,12 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Common +import CopyWithUpdates import Redux import ToolbarKit import SummarizeKit +@CopyWithUpdates struct AddressBarState: StateType, Sendable, Equatable { var windowUUID: WindowUUID var navigationActions: [ToolbarActionConfiguration] @@ -75,10 +77,10 @@ struct AddressBarState: StateType, Sendable, Equatable { isLoading: false, readerModeState: nil, canSummarize: false, - translationConfiguration: nil, didStartTyping: false, isEmptySearch: true, - alternativeSearchEngine: nil + alternativeSearchEngine: nil, + translationConfiguration: nil ) } @@ -100,10 +102,10 @@ struct AddressBarState: StateType, Sendable, Equatable { isLoading: Bool, readerModeState: ReaderModeState?, canSummarize: Bool, - translationConfiguration: TranslationConfiguration?, didStartTyping: Bool, isEmptySearch: Bool, - alternativeSearchEngine: SearchEngineModel?) { + alternativeSearchEngine: SearchEngineModel?, + translationConfiguration: TranslationConfiguration?) { self.windowUUID = windowUUID self.navigationActions = navigationActions self.leadingPageActions = leadingPageActions @@ -227,8 +229,7 @@ struct AddressBarState: StateType, Sendable, Equatable { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: [ToolbarActionConfiguration](), leadingPageActions: [ToolbarActionConfiguration](), trailingPageActions: [ToolbarActionConfiguration](), @@ -246,10 +247,9 @@ struct AddressBarState: StateType, Sendable, Equatable { isLoading: false, readerModeState: nil, canSummarize: false, - translationConfiguration: nil, didStartTyping: false, isEmptySearch: true, - alternativeSearchEngine: state.alternativeSearchEngine + translationConfiguration: nil, ) } @@ -257,29 +257,8 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleNumberOfTabsChangedAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + return state.copyWithUpdates( + browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing) ) } @@ -287,29 +266,8 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleDidSetTabScreenshotAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + return state.copyWithUpdates( + browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing) ) } @@ -319,31 +277,11 @@ struct AddressBarState: StateType, Sendable, Equatable { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, + return state.copyWithUpdates( leadingPageActions: leadingPageActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: toolbarAction.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + translationConfiguration: toolbarAction.translationConfiguration ) } @@ -357,29 +295,9 @@ struct AddressBarState: StateType, Sendable, Equatable { addressBarState: state, isEditing: state.isEditing, isEmptySearch: state.isEmptySearch) - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, + return state.copyWithUpdates( trailingPageActions: trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: toolbarAction.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine) + canSummarize: toolbarAction.canSummarize) } @MainActor @@ -391,29 +309,11 @@ struct AddressBarState: StateType, Sendable, Equatable { addressBarState: state, isEditing: state.isEditing, isEmptySearch: state.isEmptySearch) - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, + return state.copyWithUpdates( trailingPageActions: trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, lockIconImageName: lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, readerModeState: toolbarAction.readerModeState, - canSummarize: toolbarAction.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + canSummarize: toolbarAction.canSummarize ) } @@ -421,8 +321,7 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleWebsiteLoadingStateDidChangeAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -432,24 +331,7 @@ struct AddressBarState: StateType, Sendable, Equatable { trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: toolbarAction.isLoading ?? state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + isLoading: toolbarAction.isLoading ?? state.isLoading ) } @@ -459,8 +341,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let isEmptySearch = toolbarAction.url == nil - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -472,26 +353,17 @@ struct AddressBarState: StateType, Sendable, Equatable { isEditing: state.isEditing, isEmptySearch: isEmptySearch), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: state.borderPosition, url: toolbarAction.url, searchTerm: nil, - lockIconButtonA11yId: toolbarAction.lockIconButtonA11yId ?? state.lockIconButtonA11yId, - lockIconImageName: toolbarAction.lockIconImageName ?? state.lockIconImageName, + lockIconButtonA11yId: toolbarAction.lockIconButtonA11yId.map { Optional($0) } ?? .some(nil), + lockIconImageName: toolbarAction.lockIconImageName.map { Optional($0) } ?? .some(nil), lockIconNeedsTheming: toolbarAction.lockIconNeedsTheming ?? state.lockIconNeedsTheming, safeListedURLImageName: toolbarAction.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, + isEmptySearch: isEmptySearch, translationConfiguration: resolveTranslationConfig( from: toolbarAction, existingConfig: state.translationConfiguration - ), - didStartTyping: state.didStartTyping, - isEmptySearch: isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + ) ) } @@ -510,8 +382,7 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleBackForwardButtonStateChangedAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -521,24 +392,7 @@ struct AddressBarState: StateType, Sendable, Equatable { trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: nil, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + searchTerm: nil ) } @@ -546,8 +400,7 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleTraitCollectionDidChangeAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -557,24 +410,7 @@ struct AddressBarState: StateType, Sendable, Equatable { trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing) ) } @@ -582,8 +418,7 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleShowMenuWarningBadgeAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -593,24 +428,7 @@ struct AddressBarState: StateType, Sendable, Equatable { trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing) ) } @@ -618,8 +436,7 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handlePositionChangedAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), @@ -630,23 +447,7 @@ struct AddressBarState: StateType, Sendable, Equatable { addressBarState: state, isEditing: state.isEditing), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: state.isEditing), - borderPosition: toolbarAction.addressBorderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + borderPosition: toolbarAction.addressBorderPosition ) } @@ -656,8 +457,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let isEmptySearch = toolbarAction.searchTerm == nil || toolbarAction.searchTerm?.isEmpty == true - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: true), leadingPageActions: leadingPageActions(action: toolbarAction, addressBarState: state, isEditing: true), trailingPageActions: trailingPageActions(action: toolbarAction, @@ -665,23 +465,12 @@ struct AddressBarState: StateType, Sendable, Equatable { isEditing: true, isEmptySearch: isEmptySearch), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: true), - borderPosition: state.borderPosition, - url: state.url, searchTerm: toolbarAction.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, shouldShowKeyboard: true, shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: false, - isEmptySearch: isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: isEmptySearch ) } @@ -693,8 +482,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let locationText = searchTerm ?? state.url?.absoluteString let isEmptySearch = locationText == nil || locationText?.isEmpty == true - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: true), leadingPageActions: leadingPageActions(action: toolbarAction, addressBarState: state, isEditing: true), trailingPageActions: trailingPageActions(action: toolbarAction, @@ -702,23 +490,12 @@ struct AddressBarState: StateType, Sendable, Equatable { isEditing: true, isEmptySearch: isEmptySearch), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: true), - borderPosition: state.borderPosition, - url: state.url, searchTerm: searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, shouldShowKeyboard: true, shouldSelectSearchTerm: true, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: false, - isEmptySearch: isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: isEmptySearch ) } @@ -738,8 +515,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let url = toolbarAction.url ?? state.url let isEmptySearch = url == nil - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state), leadingPageActions: leadingPageActions(action: toolbarAction, addressBarState: state), trailingPageActions: trailingPageActions(action: toolbarAction, @@ -747,23 +523,13 @@ struct AddressBarState: StateType, Sendable, Equatable { isEditing: false, isEmptySearch: isEmptySearch), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: false), - borderPosition: state.borderPosition, url: url, searchTerm: nil, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: false, shouldShowKeyboard: false, shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: false, - isEmptySearch: isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: isEmptySearch ) } @@ -773,8 +539,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let isEmptySearch = toolbarAction.searchTerm == nil || toolbarAction.searchTerm?.isEmpty == true - return AddressBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( navigationActions: navigationActions(action: toolbarAction, addressBarState: state, isEditing: true), leadingPageActions: leadingPageActions(action: toolbarAction, addressBarState: state, isEditing: true), trailingPageActions: trailingPageActions(action: toolbarAction, @@ -782,23 +547,12 @@ struct AddressBarState: StateType, Sendable, Equatable { isEditing: true, isEmptySearch: isEmptySearch), browserActions: browserActions(action: toolbarAction, addressBarState: state, isEditing: true), - borderPosition: state.borderPosition, - url: state.url, searchTerm: toolbarAction.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, shouldShowKeyboard: true, shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: false, - isEmptySearch: isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: isEmptySearch ) } @@ -808,29 +562,8 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleShouldShowKeyboardAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: toolbarAction.shouldShowKeyboard ?? false, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + return state.copyWithUpdates( + shouldShowKeyboard: toolbarAction.shouldShowKeyboard ?? false ) } @@ -838,32 +571,14 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleClearSearchAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, + return state.copyWithUpdates( trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: true, isEmptySearch: true), - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, searchTerm: nil, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: true, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: true ) } @@ -871,32 +586,15 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleDidDeleteSearchTermAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, + return state.copyWithUpdates( trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: true, isEmptySearch: true), - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, - shouldShowKeyboard: state.shouldShowKeyboard, shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: true, - isEmptySearch: true, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: true ) } @@ -904,90 +602,33 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleDidEnterSearchTermAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, + return state.copyWithUpdates( trailingPageActions: trailingPageActions(action: toolbarAction, addressBarState: state, isEditing: true, isEmptySearch: false), - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, isEditing: true, - shouldShowKeyboard: state.shouldShowKeyboard, shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, didStartTyping: true, - isEmptySearch: false, - alternativeSearchEngine: state.alternativeSearchEngine + isEmptySearch: false ) } private static func handleDidSetSearchTermAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, + return state.copyWithUpdates( searchTerm: toolbarAction.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: false, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + didStartTyping: false ) } private static func handleDidStartTypingAction(state: Self, action: Action) -> Self { guard action is ToolbarAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, + return state.copyWithUpdates( shouldSelectSearchTerm: false, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: true, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine + didStartTyping: true ) } @@ -996,28 +637,7 @@ struct AddressBarState: StateType, Sendable, Equatable { let selectedSearchEngine = searchEngineSelectionAction.selectedSearchEngine else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, + return state.copyWithUpdates( alternativeSearchEngine: selectedSearchEngine ) } @@ -1025,57 +645,13 @@ struct AddressBarState: StateType, Sendable, Equatable { private static func handleDidClearAlternativeSearchEngine(state: Self, action: Action) -> Self { guard action is SearchEngineSelectionAction else { return defaultState(from: state) } - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, + return state.copyWithUpdates( alternativeSearchEngine: nil ) } static func defaultState(from state: AddressBarState) -> Self { - return AddressBarState( - windowUUID: state.windowUUID, - navigationActions: state.navigationActions, - leadingPageActions: state.leadingPageActions, - trailingPageActions: state.trailingPageActions, - browserActions: state.browserActions, - borderPosition: state.borderPosition, - url: state.url, - searchTerm: state.searchTerm, - lockIconButtonA11yId: state.lockIconButtonA11yId, - lockIconImageName: state.lockIconImageName, - lockIconNeedsTheming: state.lockIconNeedsTheming, - safeListedURLImageName: state.safeListedURLImageName, - isEditing: state.isEditing, - shouldShowKeyboard: state.shouldShowKeyboard, - shouldSelectSearchTerm: state.shouldSelectSearchTerm, - isLoading: state.isLoading, - readerModeState: state.readerModeState, - canSummarize: state.canSummarize, - translationConfiguration: state.translationConfiguration, - didStartTyping: state.didStartTyping, - isEmptySearch: state.isEmptySearch, - alternativeSearchEngine: state.alternativeSearchEngine - ) + return state.copyWithUpdates() } // MARK: - Address Toolbar Actions diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/NavigationBarState.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/NavigationBarState.swift index ba8fae4201ec7..b287dbcb8d958 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/NavigationBarState.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/NavigationBarState.swift @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Common +import CopyWithUpdates import Redux enum NavigationBarMiddleButtonType: String, Equatable, CaseIterable { @@ -28,6 +29,7 @@ enum NavigationBarMiddleButtonType: String, Equatable, CaseIterable { } } +@CopyWithUpdates struct NavigationBarState: StateType, Equatable { var windowUUID: WindowUUID var actions: [ToolbarActionConfiguration] @@ -118,8 +120,7 @@ struct NavigationBarState: StateType, Equatable { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( actions: navigationActions(action: toolbarAction, navigationBarState: state), displayBorder: displayBorder, middleButton: middleButton @@ -130,11 +131,8 @@ struct NavigationBarState: StateType, Equatable { private static func handleUrlDidChangeAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + actions: navigationActions(action: toolbarAction, navigationBarState: state) ) } @@ -142,11 +140,8 @@ struct NavigationBarState: StateType, Equatable { private static func handleNumberOfTabsChangedAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + actions: navigationActions(action: toolbarAction, navigationBarState: state) ) } @@ -154,11 +149,8 @@ struct NavigationBarState: StateType, Equatable { private static func handleDidSetTabScreenshotAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + actions: navigationActions(action: toolbarAction, navigationBarState: state) ) } @@ -166,11 +158,8 @@ struct NavigationBarState: StateType, Equatable { private static func handleBackForwardButtonStateChangedAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + actions: navigationActions(action: toolbarAction, navigationBarState: state) ) } @@ -178,11 +167,8 @@ struct NavigationBarState: StateType, Equatable { private static func handleShowMenuWarningBadgeAction(state: Self, action: Action) -> Self { guard let toolbarAction = action as? ToolbarAction else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + actions: navigationActions(action: toolbarAction, navigationBarState: state) ) } @@ -192,11 +178,8 @@ struct NavigationBarState: StateType, Equatable { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, - actions: state.actions, - displayBorder: displayBorder, - middleButton: state.middleButton + return state.copyWithUpdates( + displayBorder: displayBorder ) } @@ -206,21 +189,14 @@ struct NavigationBarState: StateType, Equatable { let middleButton = toolbarAction.middleButton else { return defaultState(from: state) } - return NavigationBarState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( actions: navigationActions(action: toolbarAction, navigationBarState: state), - displayBorder: state.displayBorder, middleButton: middleButton ) } static func defaultState(from state: NavigationBarState) -> NavigationBarState { - return NavigationBarState( - windowUUID: state.windowUUID, - actions: state.actions, - displayBorder: state.displayBorder, - middleButton: state.middleButton - ) + return state.copyWithUpdates() } // MARK: - Navigation Toolbar Actions diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarActionConfiguration.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarActionConfiguration.swift index 8f59fb38047aa..f93bcb1d2b429 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarActionConfiguration.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarActionConfiguration.swift @@ -58,6 +58,7 @@ struct ToolbarActionConfiguration: Equatable, LegacyFeatureFlaggable { actionType == .readerMode || actionType == .readerModeWithSummarizer || actionType == .summarizer || + (actionType == .translate && isSelected) || (actionType == .tabs && isShowingTopTabs == false) } } diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarMiddleware.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarMiddleware.swift index 3dbb02e5f37a9..96c06637b16b9 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarMiddleware.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/ToolbarMiddleware.swift @@ -369,6 +369,9 @@ final class ToolbarMiddleware: LegacyFeatureFlaggable { actionType: GeneralBrowserActionType.showSummarizer) store.dispatch(action) } + case .translate: + // Long-press on translate is handled in TranslationsMiddleware. + break default: break } diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/TranslationsConfiguration.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/TranslationsConfiguration.swift index a36a755c6607d..a9268b2f41705 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/TranslationsConfiguration.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Redux/TranslationsConfiguration.swift @@ -6,7 +6,7 @@ import Shared // Holds the configuration / state of the translation button on the toolbar // Whether we should show translate button and which mode (inactive, loading, active) -struct TranslationConfiguration: Equatable, LegacyFeatureFlaggable { +struct TranslationConfiguration: Equatable, FeatureFlaggable { /// This is used to configure the translation icon state. /// States: /// inactive - page has not been translated yet @@ -55,18 +55,21 @@ struct TranslationConfiguration: Equatable, LegacyFeatureFlaggable { let state: IconState? /// The language code the page was translated to, if in the active state (e.g. "en", "fr"). let translatedToLanguage: String? + /// The original language of the page before translation (e.g. "de", "fr"). + let sourceLanguage: String? // We initially set icon state as nil until we can detect the // web page and determine if we should show the translation icon // and set the icon to .inactive state. - init(prefs: Prefs, state: IconState? = nil, translatedToLanguage: String? = nil) { + init(prefs: Prefs, state: IconState? = nil, translatedToLanguage: String? = nil, sourceLanguage: String? = nil) { self.prefs = prefs self.state = state self.translatedToLanguage = translatedToLanguage + self.sourceLanguage = sourceLanguage } var isMultiLanguageFlow: Bool { - guard featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly) else { return false } + guard featureFlagsProvider.isEnabled(.translationLanguagePicker) else { return false } guard let stored = prefs.stringForKey(PrefsKeys.Settings.translationPreferredLanguages), !stored.isEmpty else { return false } return stored.components(separatedBy: ",").count != 1 @@ -76,7 +79,7 @@ struct TranslationConfiguration: Equatable, LegacyFeatureFlaggable { /// The experiment needs to be turned on and the user settings needs to be enabled /// If user has not toggled the settings, then we enable the feature by default var isTranslationFeatureEnabled: Bool { - let isExperimentOn = featureFlags.isFeatureEnabled(.translation, checking: .buildOnly) + let isExperimentOn = featureFlagsProvider.isEnabled(.translation) let isSettingsEnabled = prefs.boolForKey(PrefsKeys.Settings.translationsFeature) ?? true return isExperimentOn && isSettingsEnabled } @@ -85,5 +88,6 @@ struct TranslationConfiguration: Equatable, LegacyFeatureFlaggable { return lhs.isTranslationFeatureEnabled == rhs.isTranslationFeatureEnabled && lhs.state == rhs.state && lhs.translatedToLanguage == rhs.translatedToLanguage + && lhs.sourceLanguage == rhs.sourceLanguage } } diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/SearchBarLocationSaver.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/SearchBarLocationSaver.swift index 7a87d22a4ee7e..2715b038a656b 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/SearchBarLocationSaver.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/SearchBarLocationSaver.swift @@ -9,7 +9,7 @@ protocol SearchBarLocationSaverProtocol { func saveUserSearchBarLocation(profile: Profile, userInterfaceIdiom: UIUserInterfaceIdiom) } -struct SearchBarLocationSaver: SearchBarLocationProvider, LegacyFeatureFlaggable, SearchBarLocationSaverProtocol { +struct SearchBarLocationSaver: SearchBarLocationProvider, UserFeaturePreferenceProvider, SearchBarLocationSaverProtocol { /// Saves the search bar location position to user preferences for existing users /// that didn't have the position saved yet. For users on iPhone with version1 or version2 as layout the /// search bar location position is set to bottom, otherwise the default is used. @@ -17,8 +17,10 @@ struct SearchBarLocationSaver: SearchBarLocationProvider, LegacyFeatureFlaggable /// - profile: the user's profile /// - userInterfaceIdiom: the interface type for the device @MainActor - func saveUserSearchBarLocation(profile: Profile, - userInterfaceIdiom: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom) { + func saveUserSearchBarLocation( + profile: Profile, + userInterfaceIdiom: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom + ) { let isFreshInstall = profile.prefs.stringForKey(PrefsKeys.AppVersion.Latest) == nil let hasSearchBarPosition = profile.prefs.stringForKey(PrefsKeys.FeatureFlags.SearchBarPosition) != nil @@ -26,13 +28,10 @@ struct SearchBarLocationSaver: SearchBarLocationProvider, LegacyFeatureFlaggable guard !isFreshInstall && !hasSearchBarPosition else { return } guard userInterfaceIdiom != .pad else { - let isAtBottom = isBottomSearchBar - let searchBarPosition: SearchBarPosition = isAtBottom ? .bottom : .top - featureFlags.set(feature: .searchBarPosition, to: searchBarPosition) + userPreferences.setSearchBarPosition(.top) return } - // Set the address bar to the bottom for new users enrolled in `version1` or `version2` toolbar experiment. - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.bottom) + userPreferences.setSearchBarPosition(.bottom) } } diff --git a/firefox-ios/Client/Frontend/Browser/TopTabDisplayManager.swift b/firefox-ios/Client/Frontend/Browser/TopTabDisplayManager.swift index e70873ccb30a6..a87ddebc43192 100644 --- a/firefox-ios/Client/Frontend/Browser/TopTabDisplayManager.swift +++ b/firefox-ios/Client/Frontend/Browser/TopTabDisplayManager.swift @@ -338,6 +338,7 @@ class TopTabDisplayManager: NSObject { func performCloseAction(for tab: Tab) { guard !isDragging else { return } + // TODO: FXIOS-TODO - Why do we call get tabs here, can we remove it _ = getTabs() tabsPanelTelemetry.tabClosed(mode: tab.isPrivate ? .private : .normal) tabManager.removeTab(tab.tabUUID) diff --git a/firefox-ios/Client/Frontend/Browser/TopTabsViewController.swift b/firefox-ios/Client/Frontend/Browser/TopTabsViewController.swift index 88579c371ee35..352701e45e4fe 100644 --- a/firefox-ios/Client/Frontend/Browser/TopTabsViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/TopTabsViewController.swift @@ -18,8 +18,6 @@ protocol TopTabsDelegate: AnyObject { func topTabsDidChangeTab() @MainActor func topTabsDidPressPrivateMode() - @MainActor - func topTabsShowCloseTabsToast() } class TopTabsViewController: UIViewController, Themeable, Notifiable, LegacyFeatureFlaggable { @@ -373,7 +371,6 @@ extension TopTabsViewController: TopTabCellDelegate { func tabCellDidClose(_ cell: UICollectionViewCell) { store.dispatch(ToolbarAction(windowUUID: windowUUID, actionType: ToolbarActionType.cancelEdit)) topTabDisplayManager.closeActionPerformed(forCell: cell) - delegate?.topTabsShowCloseTabsToast() NotificationCenter.default.post(name: .TopTabsTabClosed, object: nil, userInfo: windowUUID.userInfo) store.dispatch(TopTabsAction(windowUUID: windowUUID, actionType: TopTabsActionType.didTapCloseTab)) diff --git a/firefox-ios/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift b/firefox-ios/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift index 22b054b3969cb..373d2e64b2ae9 100644 --- a/firefox-ios/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift +++ b/firefox-ios/Client/Frontend/ContextualHint/ContextualHintEligibilityUtility.swift @@ -13,7 +13,8 @@ protocol ContextualHintEligibilityUtilityProtocol { struct ContextualHintEligibilityUtility: ContextualHintEligibilityUtilityProtocol, ContextualHintPrefsKeysProvider, - SearchBarLocationProvider { + SearchBarLocationProvider, + FeatureFlaggable { var profile: Profile // For contextual hints shown in Homepage that can overlap with keyboard being raised by user interaction private var overlayState: OverlayStateProtocol? @@ -73,7 +74,7 @@ struct ContextualHintEligibilityUtility: ContextualHintEligibilityUtilityProtoco } private var canTranslationCFRBePresented: Bool { - return featureFlags.isFeatureEnabled(.translation, checking: .buildOnly) ? true : false + return featureFlagsProvider.isEnabled(.translation) } @MainActor diff --git a/firefox-ios/Client/Frontend/ContextualHint/ContextualHintViewProvider.swift b/firefox-ios/Client/Frontend/ContextualHint/ContextualHintViewProvider.swift index 986592aad950f..c7148dba0e4be 100644 --- a/firefox-ios/Client/Frontend/ContextualHint/ContextualHintViewProvider.swift +++ b/firefox-ios/Client/Frontend/ContextualHint/ContextualHintViewProvider.swift @@ -25,7 +25,9 @@ enum ContextualHintType: String { } @MainActor -class ContextualHintViewProvider: ContextualHintPrefsKeysProvider, SearchBarLocationProvider { +class ContextualHintViewProvider: ContextualHintPrefsKeysProvider, + SearchBarLocationProvider, + FeatureFlaggable { typealias CFRPrefsKeys = PrefsKeys.ContextualHints typealias CFRStrings = String.ContextualHints @@ -51,7 +53,7 @@ class ContextualHintViewProvider: ContextualHintPrefsKeysProvider, SearchBarLoca let hintEligibilityUtility = ContextualHintEligibilityUtility( with: profile, overlayState: overlayState, - isToolbarUpdateCFRFeatureEnabled: featureFlags.isFeatureEnabled(.toolbarUpdateHint, checking: .buildOnly) + isToolbarUpdateCFRFeatureEnabled: featureFlagsProvider.isEnabled(.toolbarUpdateHint) ) return hintEligibilityUtility.canPresent(hintType) diff --git a/firefox-ios/Client/Frontend/Home/Homepage/Bookmark/BookmarksSectionState.swift b/firefox-ios/Client/Frontend/Home/Homepage/Bookmark/BookmarksSectionState.swift index d88194e859aca..7bb2b5dfd57ca 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/Bookmark/BookmarksSectionState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/Bookmark/BookmarksSectionState.swift @@ -26,8 +26,8 @@ struct BookmarksSectionState: StateType, Equatable, Hashable { init(profile: Profile = AppContainer.shared.resolve(), windowUUID: WindowUUID) { // TODO: FXIOS-11412 - Move profile dependency - let shouldShowSection = LegacyFeatureFlagsManager.shared.isFeatureEnabled(.homepageBookmarksSectionDefault, - checking: .userOnly) + let userPreferences: UserFeaturePreferring = AppContainer.shared.resolve() + let shouldShowSection = userPreferences.isHomepageBookmarksSectionEnabled self.init( windowUUID: windowUUID, bookmarks: [], diff --git a/firefox-ios/Client/Frontend/Home/Homepage/Header/HeaderState.swift b/firefox-ios/Client/Frontend/Home/Homepage/Header/HeaderState.swift index d31ecd7910e59..6d81f1af846d3 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/Header/HeaderState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/Header/HeaderState.swift @@ -14,10 +14,13 @@ struct HeaderState: StateType, Equatable, Hashable { var isPrivate: Bool var showiPadSetup: Bool - init(windowUUID: WindowUUID) { + init( + windowUUID: WindowUUID, + isPrivate: Bool = false, + ) { self.init( windowUUID: windowUUID, - isPrivate: false, + isPrivate: isPrivate, showiPadSetup: false ) } @@ -41,7 +44,8 @@ struct HeaderState: StateType, Equatable, Hashable { switch action.actionType { case HomepageActionType.initialize: return handleInitializeAction(for: state, with: action) - case HomepageActionType.traitCollectionDidChange: + case HomepageActionType.traitCollectionDidChange, + HomepageActionType.viewWillAppear: return handleTraitCollectionDidChangeAction(for: state, with: action) default: return defaultState(from: state) diff --git a/firefox-ios/Client/Frontend/Home/Homepage/Header/HomepageHeaderCell.swift b/firefox-ios/Client/Frontend/Home/Homepage/Header/HomepageHeaderCell.swift index 83fa083bda94c..dfb6a85d18672 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/Header/HomepageHeaderCell.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/Header/HomepageHeaderCell.swift @@ -8,12 +8,13 @@ import Common import Shared // Header for the homepage in both normal and private mode -// Contains the firefox logo and the private browsing shortcut button -class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { +// Contains the firefox logo, and optionally the Quick Answers button +class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable, FeatureFlaggable { enum UX { static let firefoxLogoImageSize = CGSize(width: 40, height: 40) static let firefoxTextImageSize = CGSize(width: 90, height: 40) static let interImageSpacing: CGFloat = 10 + static let quickAnswersButtonSize: CGFloat = 44 static func contentWidth() -> CGFloat { return UX.firefoxLogoImageSize.width + UX.interImageSpacing + UX.firefoxTextImageSize.width @@ -22,6 +23,7 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { typealias a11y = AccessibilityIdentifiers.FirefoxHomepage.OtherButtons + private var onQuickAnswersTapped: (() -> Void)? private var headerState: HeaderState? private var hasConfiguredView = false private var headerConstraints = [NSLayoutConstraint]() @@ -35,6 +37,7 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { private lazy var logoStackView: UIStackView = .build { view in view.backgroundColor = .clear + view.alignment = .center view.accessibilityIdentifier = a11y.logoID view.accessibilityLabel = AppName.shortName.rawValue view.isAccessibilityElement = true @@ -50,6 +53,22 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { imageView.contentMode = .scaleAspectFit } + private lazy var quickAnswersButton: UIButton = .build { button in + button.configuration = .filled() + // TODO: - FXIOS-15477 Add correct acorn icon + button.configuration?.image = UIImage(systemName: "waveform") + button.configuration?.cornerStyle = .capsule + // TODO: - FXIOS-14720 Add Strings for accessibility label + button.accessibilityIdentifier = a11y.quickAnswersButton + button.adjustsImageSizeForAccessibilityContentSizeCategory = false + button.addAction( + UIAction(handler: { [weak self] _ in + self?.onQuickAnswersTapped?() + }), + for: .touchUpInside + ) + } + // MARK: - Initializers override init(frame: CGRect) { super.init(frame: frame) @@ -61,7 +80,7 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { // MARK: - UI Setup - private func setupView(with showiPadSetup: Bool) { + private func setupView(headerState: HeaderState) { if !hasConfiguredView { contentView.backgroundColor = .clear logoStackView.addArrangedSubview(logoImage) @@ -69,12 +88,18 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { logoContainerView.addSubview(logoStackView) stackContainer.addArrangedSubview(logoContainerView) + if featureFlagsProvider.isEnabled(.quickAnswers), !headerState.isPrivate { + if headerState.showiPadSetup { + // On iPad, add button directly to contentView so logo remains centered + contentView.addSubview(quickAnswersButton) + } else { + // On iPhone, add spacer view to stretch the logo and the button to leading and trailing + stackContainer.addArrangedSubview(UIView()) + stackContainer.addArrangedSubview(quickAnswersButton) + } + } contentView.addSubview(stackContainer) - - NSLayoutConstraint.activate([ - logoStackView.topAnchor.constraint(equalTo: logoContainerView.topAnchor), - logoStackView.bottomAnchor.constraint(equalTo: logoContainerView.bottomAnchor) - ]) + logoStackView.pinToSuperview() hasConfiguredView = true } @@ -87,14 +112,26 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { private func setupConstraints() { NSLayoutConstraint.deactivate(headerConstraints) + headerConstraints = [ stackContainer.topAnchor.constraint(equalTo: contentView.topAnchor), - stackContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), - stackContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), stackContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).priority(.defaultLow), + stackContainer.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), + stackContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).priority(.defaultLow), + stackContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).priority(.defaultLow), - logoContainerView.centerYAnchor.constraint(equalTo: stackContainer.centerYAnchor) + quickAnswersButton.widthAnchor.constraint(equalToConstant: UX.quickAnswersButtonSize), + quickAnswersButton.heightAnchor.constraint(equalToConstant: UX.quickAnswersButtonSize), ] + // Instead of checking on the state check if the quickAnswer button was added to the superview in order to avoid + // potential crashes. + // When the button is added to the contentView it is the iPad layout. + if quickAnswersButton.superview == contentView { + headerConstraints.append(contentsOf: [ + quickAnswersButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + quickAnswersButton.centerYAnchor.constraint(equalTo: logoContainerView.centerYAnchor) + ]) + } NSLayoutConstraint.activate(headerConstraints) } @@ -105,15 +142,17 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { logoImage.widthAnchor.constraint(equalToConstant: UX.firefoxLogoImageSize.width), logoImage.heightAnchor.constraint(equalToConstant: UX.firefoxLogoImageSize.height), logoTextImage.widthAnchor.constraint(equalToConstant: UX.firefoxTextImageSize.width), - logoTextImage.heightAnchor.constraint(equalToConstant: UX.firefoxTextImageSize.height) + logoTextImage.heightAnchor.constraint(equalToConstant: UX.firefoxTextImageSize.height), + logoContainerView.heightAnchor.constraint(greaterThanOrEqualToConstant: UX.firefoxLogoImageSize.height) ] NSLayoutConstraint.activate(logoConstraints) } - func configure(headerState: HeaderState) { + func configure(headerState: HeaderState, onQuickAnswersTapped: (() -> Void)? = nil) { self.headerState = headerState - setupView(with: headerState.showiPadSetup) + self.onQuickAnswersTapped = onQuickAnswersTapped + setupView(headerState: headerState) } // MARK: - ThemeApplicable @@ -135,5 +174,8 @@ class HomepageHeaderCell: UICollectionViewCell, ReusableCell, ThemeApplicable { .withRenderingMode(.alwaysTemplate) logoTextImage.tintColor = theme.colors.textPrimary } + + quickAnswersButton.configuration?.baseBackgroundColor = theme.colors.layer4 + quickAnswersButton.configuration?.baseForegroundColor = theme.colors.actionPrimary } } diff --git a/firefox-ios/Client/Frontend/Home/Homepage/HomepageDiffableDataSource.swift b/firefox-ios/Client/Frontend/Home/Homepage/HomepageDiffableDataSource.swift index ac4879b2c2f44..3272091ba64de 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/HomepageDiffableDataSource.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/HomepageDiffableDataSource.swift @@ -30,7 +30,7 @@ final class HomepageDiffableDataSource: case searchBar case jumpBackIn(TextColor?, JumpBackInSectionLayoutConfiguration) case bookmarks(TextColor?) - case pocket(TextColor?, String?) + case pocket(TextColor?) case spacer var canHandleLongPress: Bool { @@ -146,7 +146,7 @@ final class HomepageDiffableDataSource: } if let stories = getMerinoStories(with: state.merinoState, selectedNewsfeedCategoryID: selectedNewsfeedCategoryID) { - let pocketSection = HomeSection.pocket(textColor, selectedNewsfeedCategoryID) + let pocketSection = HomeSection.pocket(textColor) snapshot.appendSections([pocketSection]) snapshot.appendItems(stories, toSection: pocketSection) } diff --git a/firefox-ios/Client/Frontend/Home/Homepage/HomepageTabStateStore.swift b/firefox-ios/Client/Frontend/Home/Homepage/HomepageTabStateStore.swift index 127d25442ead0..41eb2eaaa0fef 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/HomepageTabStateStore.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/HomepageTabStateStore.swift @@ -7,6 +7,7 @@ import Foundation struct HomepageTabState: Equatable { var scrollOffsetY: CGFloat? var selectedNewsfeedCategoryID: String? + var newsfeedCategoryPickerOffsetX: CGFloat? } protocol HomepageTabStateStoring: AnyObject { diff --git a/firefox-ios/Client/Frontend/Home/Homepage/HomepageViewController.swift b/firefox-ios/Client/Frontend/Home/Homepage/HomepageViewController.swift index 6fff1160c1af7..0b3981aa30d45 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/HomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/HomepageViewController.swift @@ -172,12 +172,15 @@ final class HomepageViewController: UIViewController, super.viewWillAppear(animated) activeTabUUID = tabManager.selectedTab?.tabUUID - refreshHomepageDataSourceSnapshot() + refreshHomepageDataSourceSnapshot { [weak self] in + self?.updateNewsHeaderPickerState() + } /// Used as a trigger for showing a microsurvey based on viewing the homepage Experiments.events.recordEvent(BehavioralTargetingEvent.homepageViewed) store.dispatch( HomepageAction( + showiPadSetup: shouldUseiPadSetup(), windowUUID: windowUUID, actionType: HomepageActionType.viewWillAppear ) @@ -387,13 +390,7 @@ final class HomepageViewController: UIViewController, if homepageState != state { self.homepageState = state - dataSource?.updateSnapshot( - state: state, - selectedNewsfeedCategoryID: currentHomepageTabState.selectedNewsfeedCategoryID, - jumpBackInDisplayConfig: getJumpBackInDisplayConfig() - ) { [weak self] in - // Force the collection view to finish applying the latest layout before re-syncing the - // visible stories header, since transition progress depends on the resolved header frame. + refreshHomepageDataSourceSnapshot { [weak self] in self?.collectionView?.layoutIfNeeded() self?.updateNewsTransitionHeaderProgress() } @@ -571,7 +568,12 @@ final class HomepageViewController: UIViewController, switch item { case .header(let state): return configuredCell(cellType: HomepageHeaderCell.self, at: indexPath) { cell in - cell.configure(headerState: state) + cell.configure(headerState: state) { [weak self] in + self?.dispatchNavigationBrowserAction( + with: NavigationDestination(.quickAnswers), + actionType: NavigationBrowserActionType.tapOnQuickAnswersButton + ) + } cell.applyTheme(theme: currentTheme) } case .privacyNotice: @@ -661,7 +663,7 @@ final class HomepageViewController: UIViewController, at indexPath: IndexPath ) -> UICollectionViewCell { let position = indexPath.item + 1 - let currentSection = dataSource?.snapshot().sectionIdentifiers[indexPath.section] ?? .pocket(.clear, nil) + let currentSection = dataSource?.snapshot().sectionIdentifiers[indexPath.section] ?? .pocket(.clear) let totalCount = dataSource?.snapshot().numberOfItems(inSection: currentSection) return configuredCell(cellType: StoryCell.self, at: indexPath) { cell in @@ -728,6 +730,8 @@ final class HomepageViewController: UIViewController, transitionEnabled: transitionEnabled, categories: homepageState.merinoState.availableCategories, selectedNewsfeedCategoryID: currentHomepageTabState.selectedNewsfeedCategoryID, + newsfeedCategoryPickerOffsetX: currentHomepageTabState.newsfeedCategoryPickerOffsetX, + onCategoryPickerScroll: updateNewsfeedCategoryPickerOffsetX, onSelection: updatedSelectedNewsfeedCategory ) newsTransitionHeaderCell.setTransitionProgress(newsTransitionProgress()) @@ -770,7 +774,7 @@ final class HomepageViewController: UIViewController, theme: currentTheme ) return sectionLabelCell - case .pocket(let textColor, _): + case .pocket(let textColor): sectionLabelCell.configure( sectionHeaderConfiguration: MerinoState.Constants.sectionHeaderConfiguration, textColor: textColor, @@ -786,12 +790,6 @@ final class HomepageViewController: UIViewController, private func updateNewsTransitionHeaderProgress() { guard MerinoState.Constants.sectionHeaderConfiguration.style == .newsAffordance, let collectionView, - let pocketSectionIndex = dataSource?.snapshot().sectionIdentifiers.firstIndex(where: { - if case .pocket = $0 { - return true - } - return false - }), let spacerSectionIndex = dataSource?.snapshot().sectionIdentifiers.firstIndex(where: { if case .spacer = $0 { return true @@ -800,9 +798,7 @@ final class HomepageViewController: UIViewController, }), let spacerAttributes = collectionView.layoutAttributesForItem(at: IndexPath(item: 0, section: spacerSectionIndex)), - let headerView = collectionView.supplementaryView(forElementKind: UICollectionView.elementKindSectionHeader, - at: IndexPath(item: 0, section: pocketSectionIndex) - ) as? NewsTransitionHeaderCell + let headerView = getNewsTransitionHeader() else { return } @@ -1064,14 +1060,49 @@ final class HomepageViewController: UIViewController, refreshHomepageDataSourceSnapshot() } - private func refreshHomepageDataSourceSnapshot() { + private func updateNewsfeedCategoryPickerOffsetX(_ newsfeedCategoryPickerOffsetX: CGFloat) { + guard let activeTabUUID else { return } + homepageTabStateStore.updateState(for: activeTabUUID) { state in + state.newsfeedCategoryPickerOffsetX = newsfeedCategoryPickerOffsetX + } + } + + private func refreshHomepageDataSourceSnapshot(completion: (() -> Void)? = nil) { dataSource?.updateSnapshot( state: homepageState, selectedNewsfeedCategoryID: currentHomepageTabState.selectedNewsfeedCategoryID, jumpBackInDisplayConfig: getJumpBackInDisplayConfig() + ) { + completion?() + } + } + + /// Applies the active `HomepageTabState`'s relevant properties to the category picker without rebuilding the section. + /// This keeps the category picker selection and horizontal offset in sync across tab switches + private func updateNewsHeaderPickerState() { + guard let headerView = getNewsTransitionHeader() else { return } + headerView.updatePickerState( + selectedNewsfeedCategoryID: currentHomepageTabState.selectedNewsfeedCategoryID, + newsfeedCategoryPickerOffsetX: currentHomepageTabState.newsfeedCategoryPickerOffsetX ) } + private func getNewsTransitionHeader() -> NewsTransitionHeaderCell? { + guard let collectionView, + let pocketSectionIndex = dataSource?.snapshot().sectionIdentifiers.firstIndex(where: { + if case .pocket = $0 { return true } + return false + }) + else { + return nil + } + + return collectionView.supplementaryView( + forElementKind: UICollectionView.elementKindSectionHeader, + at: IndexPath(item: 0, section: pocketSectionIndex) + ) as? NewsTransitionHeaderCell + } + private func getSiteForContextMenu(for item: HomepageItem) -> Site? { switch item { case .topSite(let state, _): diff --git a/firefox-ios/Client/Frontend/Home/Homepage/JumpBackIn/JumpBackInSectionState.swift b/firefox-ios/Client/Frontend/Home/Homepage/JumpBackIn/JumpBackInSectionState.swift index adc506359aff9..ec432f14a6c18 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/JumpBackIn/JumpBackInSectionState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/JumpBackIn/JumpBackInSectionState.swift @@ -28,16 +28,14 @@ struct JumpBackInSectionState: StateType, Equatable, Hashable { init( profile: Profile = AppContainer.shared.resolve(), + userPreferences: UserFeaturePreferring = AppContainer.shared.resolve(), windowUUID: WindowUUID ) { - // TODO: FXIOS-11412 - Move profile dependency - let shouldShowSection = LegacyFeatureFlagsManager.shared.isFeatureEnabled(.homepageJumpBackinSectionDefault, - checking: .userOnly) self.init( windowUUID: windowUUID, jumpBackInTabs: [], mostRecentSyncedTab: nil, - shouldShowSection: shouldShowSection + shouldShowSection: userPreferences.isHomepageJumpBackInSectionEnabled ) } diff --git a/firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageSectionLayoutProvider.swift b/firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageSectionLayoutProvider.swift index dab7bc3e0896e..c1239a3fa7d37 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageSectionLayoutProvider.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageSectionLayoutProvider.swift @@ -33,7 +33,6 @@ final class HomepageSectionLayoutProvider: LegacyFeatureFlaggable { } struct HeaderConstants { - static let estimatedHeight: CGFloat = 40 static let bottomSpacing: CGFloat = 30 } @@ -192,22 +191,18 @@ final class HomepageSectionLayoutProvider: LegacyFeatureFlaggable { private func createHeaderSectionLayout( for environment: NSCollectionLayoutEnvironment ) -> NSCollectionLayoutSection { - let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(UX.HeaderConstants.estimatedHeight), + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(UX.standardSingleItemHeight)) let item = NSCollectionLayoutItem(layoutSize: itemSize) - let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(UX.HeaderConstants.estimatedHeight), + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(UX.standardSingleItemHeight)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1) let section = NSCollectionLayoutSection(group: group) - let containerWidth = environment.container.contentSize.width - let effectiveInsets = environment.container.effectiveContentInsets - let headerWidth = HomepageHeaderCell.UX.contentWidth() - let availableWidth = max(0, containerWidth - effectiveInsets.leading - effectiveInsets.trailing) - let horizontalInset = max(0, (availableWidth - headerWidth) / 2) + let horizontalInset = UX.leadingInset(traitCollection: environment.traitCollection) section.contentInsets = NSDirectionalEdgeInsets( top: 0, diff --git a/firefox-ios/Client/Frontend/Home/Homepage/Redux/HomepageMiddleware.swift b/firefox-ios/Client/Frontend/Home/Homepage/Redux/HomepageMiddleware.swift index df693a3c40113..22c8567866bd2 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/Redux/HomepageMiddleware.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/Redux/HomepageMiddleware.swift @@ -9,7 +9,7 @@ import Common /// Middleware to handle generic homepage related actions /// If this gets too big, can split out notifications and feature flags @MainActor -final class HomepageMiddleware: LegacyFeatureFlaggable, Notifiable { +final class HomepageMiddleware: FeatureFlaggable, Notifiable { private let profile: Profile private let homepageTelemetry: HomepageTelemetry private let privacyNoticeHelper: PrivacyNoticeHelperProtocol @@ -102,7 +102,7 @@ final class HomepageMiddleware: LegacyFeatureFlaggable, Notifiable { for device: UIUserInterfaceIdiom = UIDevice.current.userInterfaceIdiom, and isLandscape: Bool = UIWindow.isLandscape ) -> Bool { - let isHomepageSearchEnabled = featureFlags.isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) + let isHomepageSearchEnabled = featureFlagsProvider.isEnabled(.homepageSearchBar) let isCompact = device == .phone && !isLandscape guard isHomepageSearchEnabled, isCompact else { diff --git a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift index 973c42bb6dfa3..e7c42216baf4c 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/LabelButtonHeaderView.swift @@ -32,6 +32,10 @@ class LabelButtonHeaderView: UIView, ThemeApplicable, Notifiable { button.isHidden = true button.setContentHuggingPriority(.required, for: .horizontal) button.setContentCompressionResistancePriority(.required, for: .horizontal) + + var updatedConfiguration = button.configuration + updatedConfiguration?.titleLineBreakMode = .byTruncatingTail + button.configuration = updatedConfiguration } // MARK: - Variables diff --git a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/NewsTransitionHeaderCell.swift b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/NewsTransitionHeaderCell.swift index f16e64d2819ed..2b24a99a8332d 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/NewsTransitionHeaderCell.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/NewsTransitionHeaderCell.swift @@ -80,6 +80,8 @@ final class NewsTransitionHeaderCell: UICollectionReusableView, transitionEnabled: Bool = true, categories: [MerinoCategoryConfiguration] = [], selectedNewsfeedCategoryID: String? = nil, + newsfeedCategoryPickerOffsetX: CGFloat? = nil, + onCategoryPickerScroll: ((CGFloat) -> Void)? = nil, onSelection: (@MainActor @Sendable (String?) -> Void)? = nil ) { self.transitionEnabled = transitionEnabled @@ -94,6 +96,8 @@ final class NewsTransitionHeaderCell: UICollectionReusableView, storyCategoryPickerView.configure( categories: categories, selectedNewsfeedCategoryID: selectedNewsfeedCategoryID, + newsfeedCategoryPickerOffsetX: newsfeedCategoryPickerOffsetX, + onScroll: onCategoryPickerScroll, onSelection: onSelection ) storyCategoryPickerView.applyTheme(theme: theme) @@ -112,6 +116,16 @@ final class NewsTransitionHeaderCell: UICollectionReusableView, updateViewState() } + func updatePickerState( + selectedNewsfeedCategoryID: String?, + newsfeedCategoryPickerOffsetX: CGFloat? + ) { + storyCategoryPickerView.applyNewsfeedPickerState( + selectedNewsfeedCategoryID: selectedNewsfeedCategoryID, + newsfeedCategoryPickerOffsetX: newsfeedCategoryPickerOffsetX + ) + } + func applyTheme(theme: Theme) { newsAffordanceContentView.applyTheme(theme: theme) sectionTitleHeaderView.applyTheme(theme: theme) @@ -119,7 +133,7 @@ final class NewsTransitionHeaderCell: UICollectionReusableView, } private func setupLayout() { - clipsToBounds = true + clipsToBounds = false sectionTitleStackView.addArrangedSubview(sectionTitleHeaderView) sectionTitleStackView.addArrangedSubview(storyCategoryPickerView) diff --git a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/StoryCategoryPickerView.swift b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/StoryCategoryPickerView.swift index f50a830076c22..8247b5588a79c 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/StoryCategoryPickerView.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/SectionHeader/StoryCategoryPickerView.swift @@ -28,7 +28,9 @@ final class StoryCategoryPickerView: UIView, ThemeApplicable { func configure( categories: [MerinoCategoryConfiguration], selectedNewsfeedCategoryID: String?, - onSelection: (@MainActor (String?) -> Void)? + newsfeedCategoryPickerOffsetX: CGFloat? = nil, + onScroll: ((CGFloat) -> Void)? = nil, + onSelection: (@MainActor (String?) -> Void)? = nil ) { let items = pickerItems(from: categories) let selectedPickerID = selectedNewsfeedCategoryID ?? Self.allCategoryID @@ -36,6 +38,8 @@ final class StoryCategoryPickerView: UIView, ThemeApplicable { chipPickerView.configure( items: items, selectedID: selectedPickerID, + contentOffsetX: newsfeedCategoryPickerOffsetX ?? 0, + onScroll: onScroll, onSelection: { selectedID in onSelection?(selectedID == Self.allCategoryID ? nil : selectedID) } @@ -47,6 +51,11 @@ final class StoryCategoryPickerView: UIView, ThemeApplicable { chipPickerView.applyTheme(theme: theme) } + func applyNewsfeedPickerState(selectedNewsfeedCategoryID: String?, newsfeedCategoryPickerOffsetX: CGFloat?) { + chipPickerView.updateSelectedID(selectedNewsfeedCategoryID ?? Self.allCategoryID) + chipPickerView.updateContentOffsetX(newsfeedCategoryPickerOffsetX ?? 0) + } + private func setupLayout() { addSubview(chipPickerView) diff --git a/firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesManager.swift b/firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesManager.swift index 5ba0f3795aef4..d1f99fa63b433 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesManager.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesManager.swift @@ -44,7 +44,7 @@ protocol TopSitesManagerInterface: Sendable { } /// Manager to fetch the top sites data, the data gets updated from notifications on specific user actions -final class TopSitesManager: TopSitesManagerInterface, LegacyFeatureFlaggable { +final class TopSitesManager: TopSitesManagerInterface, UserFeaturePreferenceProvider { private let logger: Logger private let profile: Profile private let googleTopSiteManager: GoogleTopSiteManagerProvider @@ -122,7 +122,7 @@ final class TopSitesManager: TopSitesManagerInterface, LegacyFeatureFlaggable { } private var shouldLoadSponsoredTiles: Bool { - return featureFlags.isFeatureEnabled(.hntSponsoredShortcuts, checking: .userOnly) + return userPreferences.isSponsoredShortcutsEnabled } @MainActor diff --git a/firefox-ios/Client/Frontend/Home/Homepage/WorldCup/WorldCupAction.swift b/firefox-ios/Client/Frontend/Home/Homepage/WorldCup/WorldCupAction.swift new file mode 100644 index 0000000000000..1015fdbe5d335 --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Homepage/WorldCup/WorldCupAction.swift @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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 Foundation +import Redux +import Common + +struct WorldCupAction: Action { + let windowUUID: WindowUUID + let actionType: any ActionType + let shouldShowHomepageWorldCupSection: Bool +} + +enum WorldCupActionType: ActionType { + case didChangeHomepageSettings +} diff --git a/firefox-ios/Client/Frontend/Home/PrivateHome/PrivateHomepageViewController.swift b/firefox-ios/Client/Frontend/Home/PrivateHome/PrivateHomepageViewController.swift index 3f385bea0f9a5..73c72fcbe14b9 100644 --- a/firefox-ios/Client/Frontend/Home/PrivateHome/PrivateHomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/PrivateHome/PrivateHomepageViewController.swift @@ -82,7 +82,7 @@ final class PrivateHomepageViewController: UIViewController, private lazy var homepageHeaderCell: HomepageHeaderCell = { let header = HomepageHeaderCell() header.applyTheme(theme: themeManager.getCurrentTheme(for: windowUUID)) - header.configure(headerState: HeaderState(windowUUID: windowUUID)) + header.configure(headerState: HeaderState(windowUUID: windowUUID, isPrivate: true)) return header }() diff --git a/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsCallbackTelemetry.swift b/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsCallbackTelemetry.swift index 71ded10502e92..c7380fb2cd364 100644 --- a/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsCallbackTelemetry.swift +++ b/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsCallbackTelemetry.swift @@ -14,7 +14,7 @@ protocol UnifiedAdsCallbackTelemetry { func sendClickTelemetry(tileSite: Site, position: Int) } -final class DefaultUnifiedAdsCallbackTelemetry: UnifiedAdsCallbackTelemetry, LegacyFeatureFlaggable { +final class DefaultUnifiedAdsCallbackTelemetry: UnifiedAdsCallbackTelemetry, FeatureFlaggable { private let adsClient: MozAdsClientProtocol private let networking: UnifiedTileNetworking private let logger: Logger @@ -33,7 +33,7 @@ final class DefaultUnifiedAdsCallbackTelemetry: UnifiedAdsCallbackTelemetry, Leg } private var isAdsClientEnabled: Bool { - return featureFlags.isFeatureEnabled(.adsClient, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.adsClient) } /// Impression telemetry can only be sent for `Site`s with `SiteType` `.sponsoredSite`. @@ -45,7 +45,7 @@ final class DefaultUnifiedAdsCallbackTelemetry: UnifiedAdsCallbackTelemetry, Leg if isAdsClientEnabled { do { - try adsClient.recordImpression(impressionUrl: siteInfo.impressionURL) + try adsClient.recordImpression(impressionUrl: siteInfo.impressionURL, options: nil) } catch { logger.log("Ads client recordImpression failed, falling back to legacy: \(error)", level: .warning, @@ -67,7 +67,7 @@ final class DefaultUnifiedAdsCallbackTelemetry: UnifiedAdsCallbackTelemetry, Leg if isAdsClientEnabled { do { - try adsClient.recordClick(clickUrl: siteInfo.clickURL) + try adsClient.recordClick(clickUrl: siteInfo.clickURL, options: nil) } catch { logger.log("Ads client recordClick failed, falling back to legacy: \(error)", level: .warning, diff --git a/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsProvider.swift b/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsProvider.swift index 12b4836791a76..55e93bf82774c 100644 --- a/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsProvider.swift +++ b/firefox-ios/Client/Frontend/Home/UnifiedAds/UnifiedAdsProvider.swift @@ -25,7 +25,7 @@ extension UnifiedAdsProviderInterface { } } -final class UnifiedAdsProvider: URLCaching, UnifiedAdsProviderInterface, LegacyFeatureFlaggable, Sendable { +final class UnifiedAdsProvider: URLCaching, UnifiedAdsProviderInterface, FeatureFlaggable, Sendable { private let adsClient: MozAdsClientProtocol private static let prodResourceEndpoint = "https://ads.mozilla.org/v1/ads" static let stagingResourceEndpoint = "https://ads.allizom.org/v1/ads" @@ -67,7 +67,7 @@ final class UnifiedAdsProvider: URLCaching, UnifiedAdsProviderInterface, LegacyF func fetchTiles(timestamp: Shared.Timestamp = Date.now(), completion: @escaping @Sendable (UnifiedTileResult) -> Void) { - if featureFlags.isFeatureEnabled(.adsClient, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.adsClient) { fetchTilesWithAdsClient(completion: completion) } else { guard let request = buildRequest() else { diff --git a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift index b9b6d28c14586..b79db8af67c5f 100644 --- a/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift +++ b/firefox-ios/Client/Frontend/Library/Bookmarks/BookmarksViewController.swift @@ -15,7 +15,7 @@ final class BookmarksViewController: SiteTableViewController, CanRemoveQuickActionBookmark, UITableViewDropDelegate, Notifiable, - LegacyFeatureFlaggable { + FeatureFlaggable { struct UX { static let FolderIconSize = CGSize(width: 24, height: 24) static let RowFlashDelay: TimeInterval = 0.4 @@ -57,7 +57,7 @@ final class BookmarksViewController: SiteTableViewController, } private var isBookmarksSearchEnabled: Bool { - featureFlags.isFeatureEnabled(.bookmarksSearchFeature, checking: .buildOnly) + featureFlagsProvider.isEnabled(.bookmarksSearchFeature) } private var toolbarButtonItems: [UIBarButtonItem] { @@ -495,6 +495,14 @@ final class BookmarksViewController: SiteTableViewController, bottomStackView.addKeyboardSpacer(spacerHeight: spacerHeight) } + private func updateBottomSearchBarLayout(isHidden: Bool) { + bottomStackView.isHidden = isHidden + + let bottomInset = isHidden ? 0 : searchbar.bounds.height + tableView.contentInset.bottom = bottomInset + tableView.verticalScrollIndicatorInsets.bottom = bottomInset + } + private func setupEmptyStateView() { view.addSubview(a11yEmptyStateScrollView) a11yEmptyStateScrollView.addSubview(emptyStateView) @@ -921,7 +929,7 @@ extension BookmarksViewController: UISearchBarDelegate { func startSearchState() { updatePanelState(newState: .bookmarks(state: .search)) - bottomStackView.isHidden = false + updateBottomSearchBarLayout(isHidden: false) searchbar.becomeFirstResponder() sendPanelChangeNotification() } @@ -931,7 +939,7 @@ extension BookmarksViewController: UISearchBarDelegate { searchbar.text = "" searchbar.resignFirstResponder() - bottomStackView.isHidden = true + updateBottomSearchBarLayout(isHidden: true) // Transition back to non-searching state updatePanelState(newState: viewModel.isRootNode diff --git a/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift b/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift index 29d69162873f8..ebd29183cc135 100644 --- a/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift +++ b/firefox-ios/Client/Frontend/Library/Bookmarks/Legacy/BookmarksPanelViewModel.swift @@ -45,13 +45,13 @@ let LocalizedRootBookmarkFolderStrings = [ ] @MainActor -final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol { +final class BookmarksPanelViewModel: BookmarksPanelViewModelProtocol, FeatureFlaggable { enum BookmarksSection: Int, CaseIterable { case bookmarks } private var isBookmarksSearchEnabled: Bool { - LegacyFeatureFlagsManager.shared.isFeatureEnabled(.bookmarksSearchFeature, checking: .buildOnly) + featureFlagsProvider.isEnabled(.bookmarksSearchFeature) } var isRootNode: Bool { diff --git a/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift index d3488bb0038b8..3adff1a3de228 100644 --- a/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift +++ b/firefox-ios/Client/Frontend/Microsurvey/Prompt/MicrosurveyPromptState.swift @@ -5,7 +5,9 @@ import Foundation import Redux import Common +import CopyWithUpdates +@CopyWithUpdates struct MicrosurveyPromptState: StateType, Equatable { var windowUUID: WindowUUID var showPrompt: Bool @@ -48,18 +50,14 @@ struct MicrosurveyPromptState: StateType, Equatable { } static func defaultState(from state: MicrosurveyPromptState) -> MicrosurveyPromptState { - return MicrosurveyPromptState( - windowUUID: state.windowUUID, - showPrompt: state.showPrompt, - showSurvey: false, - model: state.model + return state.copyWithUpdates( + showSurvey: false ) } private static func handleInitializeAction(state: Self, action: Action) -> Self { let model = (action as? MicrosurveyPromptMiddlewareAction)?.microsurveyModel - return MicrosurveyPromptState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( showPrompt: true, showSurvey: false, model: model @@ -67,20 +65,16 @@ struct MicrosurveyPromptState: StateType, Equatable { } private static func handleClosePromptAction(state: Self) -> Self { - return MicrosurveyPromptState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( showPrompt: false, - showSurvey: false, - model: state.model + showSurvey: false ) } private static func handleContinueToSurveyAction(state: Self) -> Self { - return MicrosurveyPromptState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( showPrompt: true, - showSurvey: true, - model: state.model + showSurvey: true ) } } diff --git a/firefox-ios/Client/Frontend/Microsurvey/Survey/MicrosurveyState.swift b/firefox-ios/Client/Frontend/Microsurvey/Survey/MicrosurveyState.swift index 742754d66c5b8..96f32c4eb80df 100644 --- a/firefox-ios/Client/Frontend/Microsurvey/Survey/MicrosurveyState.swift +++ b/firefox-ios/Client/Frontend/Microsurvey/Survey/MicrosurveyState.swift @@ -4,7 +4,9 @@ import Redux import Common +import CopyWithUpdates +@CopyWithUpdates struct MicrosurveyState: ScreenState { var windowUUID: WindowUUID var shouldDismiss: Bool @@ -20,11 +22,7 @@ struct MicrosurveyState: ScreenState { return } - self.init( - windowUUID: microsurveyState.windowUUID, - shouldDismiss: microsurveyState.shouldDismiss, - showPrivacy: microsurveyState.showPrivacy - ) + self = microsurveyState.copyWithUpdates() } init( @@ -51,14 +49,12 @@ struct MicrosurveyState: ScreenState { switch action.actionType { case MicrosurveyActionType.closeSurvey: - return MicrosurveyState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( shouldDismiss: true, showPrivacy: false ) case MicrosurveyActionType.tapPrivacyNotice: - return MicrosurveyState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( shouldDismiss: false, showPrivacy: true ) @@ -68,8 +64,7 @@ struct MicrosurveyState: ScreenState { } static func defaultState(from state: MicrosurveyState) -> MicrosurveyState { - return MicrosurveyState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( shouldDismiss: false, showPrivacy: false ) diff --git a/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageFeatureFlag.swift b/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageFeatureFlag.swift index 028ef7acd3098..c4bd5bb7e4f65 100644 --- a/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageFeatureFlag.swift +++ b/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageFeatureFlag.swift @@ -4,19 +4,19 @@ import Foundation -struct NativeErrorPageFeatureFlag: LegacyFeatureFlaggable { +struct NativeErrorPageFeatureFlag: FeatureFlaggable { var isNativeErrorPageEnabled: Bool { - return featureFlags.isFeatureEnabled(.nativeErrorPage, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.nativeErrorPage) } /// Temporary flag for showing no internet connection native error page only. var isNICErrorPageEnabled: Bool { - return featureFlags.isFeatureEnabled(.nativeErrorPage, checking: .buildOnly) && - featureFlags.isFeatureEnabled(.noInternetConnectionErrorPage, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.nativeErrorPage) && + featureFlagsProvider.isEnabled(.noInternetConnectionErrorPage) } /// Flag for showing bad certificate domain native error page var isBadCertDomainErrorPageEnabled: Bool { - return featureFlags.isFeatureEnabled(.badCertDomainErrorPage, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.badCertDomainErrorPage) } } diff --git a/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageState.swift b/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageState.swift index b6b707fde613a..90b7ec83ac956 100644 --- a/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageState.swift +++ b/firefox-ios/Client/Frontend/NativeErrorPage/NativeErrorPageState.swift @@ -2,9 +2,11 @@ // 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 CopyWithUpdates import Redux import Common +@CopyWithUpdates struct NativeErrorPageState: ScreenState { var windowUUID: WindowUUID var title: String @@ -63,8 +65,7 @@ struct NativeErrorPageState: ScreenState { guard let action = action as? NativeErrorPageAction, let model = action.nativePageErrorModel else { return defaultState(from: state) } - return NativeErrorPageState( - windowUUID: state.windowUUID, + return state.copyWithUpdates( title: model.errorTitle, description: model.errorDescription, foxImage: model.foxImageName, @@ -78,14 +79,6 @@ struct NativeErrorPageState: ScreenState { } static func defaultState(from state: NativeErrorPageState) -> NativeErrorPageState { - return NativeErrorPageState( - windowUUID: state.windowUUID, - title: state.title, - description: state.description, - foxImage: state.foxImage, - url: state.url, - advancedSection: state.advancedSection, - showGoBackButton: state.showGoBackButton - ) + return state.copyWithUpdates() } } diff --git a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift index 1d4855f461cea..2b7a5c34a1d8b 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Protocols/OnboardingService.swift @@ -10,7 +10,7 @@ import ComponentLibrary import OnboardingKit @MainActor -final class OnboardingService: LegacyFeatureFlaggable { +final class OnboardingService: UserFeaturePreferenceProvider { // MARK: - Properties private weak var delegate: OnboardingServiceDelegate? private weak var navigationDelegate: OnboardingNavigationDelegate? @@ -153,11 +153,11 @@ final class OnboardingService: LegacyFeatureFlaggable { case .themeSystemDefault: themeManager.setSystemTheme(isOn: true) case .toolbarBottom: - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.bottom) + userPreferences.setSearchBarPosition(.bottom) let notificationObject = [PrefsKeys.FeatureFlags.SearchBarPosition: SearchBarPosition.bottom] notificationCenter.post(name: .SearchBarPositionDidChange, withObject: notificationObject) case .toolbarTop: - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.top) + userPreferences.setSearchBarPosition(.top) let notificationObject = [PrefsKeys.FeatureFlags.SearchBarPosition: SearchBarPosition.top] notificationCenter.post(name: .SearchBarPositionDidChange, withObject: notificationObject) } diff --git a/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift b/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift index b5ff71bcfad78..963024b211762 100644 --- a/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift +++ b/firefox-ios/Client/Frontend/Onboarding/Views/IntroViewController.swift @@ -12,7 +12,7 @@ class IntroViewController: UIViewController, OnboardingViewControllerProtocol, Themeable, Notifiable, - LegacyFeatureFlaggable, + UserFeaturePreferenceProvider, StoreSubscriber { struct UX { static let closeButtonSize: CGFloat = 30 @@ -376,11 +376,11 @@ extension IntroViewController: OnboardingCardDelegate { case .themeSystemDefault: turnSystemTheme(on: true) case .toolbarBottom: - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.bottom) + userPreferences.setSearchBarPosition(.bottom) let notificationObject = [PrefsKeys.FeatureFlags.SearchBarPosition: SearchBarPosition.bottom] notificationCenter.post(name: .SearchBarPositionDidChange, withObject: notificationObject) case .toolbarTop: - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.top) + userPreferences.setSearchBarPosition(.top) let notificationObject = [PrefsKeys.FeatureFlags.SearchBarPosition: SearchBarPosition.top] notificationCenter.post(name: .SearchBarPositionDidChange, withObject: notificationObject) } diff --git a/firefox-ios/Client/Frontend/PasswordGenerator/PasswordGeneratorState.swift b/firefox-ios/Client/Frontend/PasswordGenerator/PasswordGeneratorState.swift index d73f3343657b6..543be252074df 100644 --- a/firefox-ios/Client/Frontend/PasswordGenerator/PasswordGeneratorState.swift +++ b/firefox-ios/Client/Frontend/PasswordGenerator/PasswordGeneratorState.swift @@ -5,7 +5,9 @@ import Foundation import Redux import Common +import CopyWithUpdates +@CopyWithUpdates struct PasswordGeneratorState: ScreenState { var windowUUID: WindowUUID var password: String @@ -21,11 +23,7 @@ struct PasswordGeneratorState: ScreenState { return } - self.init( - windowUUID: passwordGeneratorState.windowUUID, - password: passwordGeneratorState.password, - passwordHidden: passwordGeneratorState.passwordHidden - ) + self = passwordGeneratorState.copyWithUpdates() } init( @@ -48,22 +46,19 @@ struct PasswordGeneratorState: ScreenState { else { return defaultState(from: state) } - return PasswordGeneratorState( - windowUUID: action.windowUUID, - password: password, - passwordHidden: state.passwordHidden) + return state.copyWithUpdates( + password: password + ) case PasswordGeneratorActionType.hidePassword: - return PasswordGeneratorState( - windowUUID: action.windowUUID, - password: state.password, - passwordHidden: true) + return state.copyWithUpdates( + passwordHidden: true + ) case PasswordGeneratorActionType.showPassword: - return PasswordGeneratorState( - windowUUID: action.windowUUID, - password: state.password, - passwordHidden: false) + return state.copyWithUpdates( + passwordHidden: false + ) default: return defaultState(from: state) @@ -71,10 +66,6 @@ struct PasswordGeneratorState: ScreenState { } static func defaultState(from state: PasswordGeneratorState) -> PasswordGeneratorState { - return PasswordGeneratorState( - windowUUID: state.windowUUID, - password: state.password, - passwordHidden: state.passwordHidden - ) + return state.copyWithUpdates() } } diff --git a/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsModel.swift b/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsModel.swift index 1951d90bbedb1..8fe5c66186223 100644 --- a/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsModel.swift +++ b/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsModel.swift @@ -5,7 +5,9 @@ import Shared import Common -class AIControlsModel: ObservableObject, LegacyFeatureFlaggable { +class AIControlsModel: ObservableObject, + FeatureFlaggable, + UserFeaturePreferenceProvider { let windowUUID: WindowUUID @Published var killSwitchIsOn = false @Published var translationEnabled: Bool @@ -30,6 +32,18 @@ class AIControlsModel: ObservableObject, LegacyFeatureFlaggable { ) }() + let blockedStatusDescription = { + try? AttributedString( + markdown: .Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription + ) + }() + + let availableStatusDescription = { + try? AttributedString( + markdown: .Settings.AIControls.AIPoweredFeaturesSection.AvailableStatusDescription + ) + }() + let blockAIEnhancementsDescription: String = { String( format: .Settings.AIControls.BlockAIEnhancementsDescription, @@ -66,9 +80,9 @@ class AIControlsModel: ObservableObject, LegacyFeatureFlaggable { pageSummariesEnabled = self.summarizerConfiguration.isSummarizeFeatureToggledOn pageSummariesVisible = self.summarizerConfiguration.isSummarizeFeatureEnabled - translationsVisible = featureFlags.isFeatureEnabled(.translation, checking: .buildOnly) + translationsVisible = featureFlagsProvider.isEnabled(.translation) - killSwitchIsOn = featureFlags.isFeatureEnabled(.aiKillSwitch, checking: .buildAndUser) + killSwitchIsOn = featureFlagsProvider.isEnabled(.aiKillSwitch) && userPreferences.isAIKillSwitchEnabled } @MainActor @@ -76,12 +90,6 @@ class AIControlsModel: ObservableObject, LegacyFeatureFlaggable { prefs.setBool(newValue, forKey: PrefsKeys.Settings.aiKillSwitchFeature) pageSummariesEnabled = !newValue translationEnabled = !newValue - prefs.setBool(!newValue, forKey: PrefsKeys.Summarizer.summarizeContentFeature) - store.dispatch(TranslationSettingsViewAction( - newSettingValue: !newValue, - windowUUID: windowUUID, - actionType: TranslationSettingsViewActionType.toggleTranslationsEnabled - )) } @MainActor diff --git a/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsSettingsView.swift b/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsSettingsView.swift index 3c93fc544f177..ee5ab90891ffd 100644 --- a/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsSettingsView.swift +++ b/firefox-ios/Client/Frontend/Settings/AIControls/AIControlsSettingsView.swift @@ -32,7 +32,7 @@ struct AIControlsSettingsView: View, ThemeApplicable { Text(aiControlsModel.blockAIEnhancementsDescription) .font(FXFontStyles.Regular.caption1.scaledSwiftUIFont()) .foregroundStyle(themeColors.textSecondary.color) - .padding(.leading) + .padding(.leading, UX.padding) if let url = aiControlsModel.headerLinkInfo.url { Link( aiControlsModel.blockAIEnhancementsLinkInfo.label, @@ -50,15 +50,10 @@ struct AIControlsSettingsView: View, ThemeApplicable { if aiControlsModel.hasVisibleAIFeatures { aiFeaturesControls } + if aiControlsModel.hasVisibleAIFeatures { + aiFeaturesControlsStatusDescription + } }.padding(.horizontal, UX.padding) - if aiControlsModel.hasVisibleAIFeatures { - VStack(alignment: .leading, spacing: UX.rowSpacing) { - Text(.init(.Settings.AIControls.AIPoweredFeaturesSection.AvailableStatusDescription)) - .font(FXFontStyles.Regular.caption1.scaledSwiftUIFont()) - Text(.init(.Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription)) - .font(FXFontStyles.Regular.caption1.scaledSwiftUIFont()) - }.padding(.horizontal, UX.padding*2) - } } .background(themeColors.layer1.color) .onChange(of: aiControlsModel.killSwitchIsOn, perform: { newValue in @@ -70,6 +65,9 @@ struct AIControlsSettingsView: View, ThemeApplicable { .onChange(of: aiControlsModel.pageSummariesEnabled, perform: { newValue in aiControlsModel.togglePageSummariesFeature(to: newValue) }) + .onAppear { + applyTheme(theme: themeManager.getCurrentTheme(for: aiControlsModel.windowUUID)) + } .onReceive(NotificationCenter.default.publisher(for: .ThemeDidChange)) { notification in guard let uuid = notification.windowUUID, uuid == aiControlsModel.windowUUID else { return } applyTheme(theme: themeManager.getCurrentTheme(for: aiControlsModel.windowUUID)) @@ -113,6 +111,7 @@ struct AIControlsSettingsView: View, ThemeApplicable { Toggle(isOn: $aiControlsModel.killSwitchIsOn) { Text(verbatim: .Settings.AIControls.BlockAIEnhancementsTitle) .font(FXFontStyles.Regular.body.scaledSwiftUIFont()) + .foregroundStyle(themeColors.textPrimary.color) } .tint(themeColors.actionPrimary.color) } @@ -139,7 +138,7 @@ struct AIControlsSettingsView: View, ThemeApplicable { Text(verbatim: .Settings.AIControls.AIPoweredFeaturesSection.Title) .font(.caption) .foregroundStyle(themeColors.textSecondary.color) - .padding(.leading) + .padding(.leading, UX.padding) RoundedCard( background: themeColors.layer5.color, cornerRadius: UX.cornerRadius, @@ -179,6 +178,23 @@ struct AIControlsSettingsView: View, ThemeApplicable { } } + @ViewBuilder + var aiFeaturesControlsStatusDescription: some View { + VStack(alignment: .leading, spacing: UX.rowSpacing) { + if let text = aiControlsModel.availableStatusDescription { + Text(text) + .font(FXFontStyles.Regular.caption1.scaledSwiftUIFont()) + .foregroundStyle(themeColors.textSecondary.color) + } + if let text = aiControlsModel.blockedStatusDescription { + Text(text) + .font(FXFontStyles.Regular.caption1.scaledSwiftUIFont()) + .foregroundStyle(themeColors.textSecondary.color) + } + } + .padding(.leading, UX.padding) + } + @ViewBuilder func aiFeatureToggleStatus(isEnabled: Bool) -> some View { if isEnabled { diff --git a/firefox-ios/Client/Frontend/Settings/AppIconSelection/AppIconSelectionView.swift b/firefox-ios/Client/Frontend/Settings/AppIconSelection/AppIconSelectionView.swift index 82989e17b0fa7..62ecf4b0ed660 100644 --- a/firefox-ios/Client/Frontend/Settings/AppIconSelection/AppIconSelectionView.swift +++ b/firefox-ios/Client/Frontend/Settings/AppIconSelection/AppIconSelectionView.swift @@ -5,7 +5,7 @@ import SwiftUI import Common -struct AppIconSelectionView: View, ThemeApplicable, LegacyFeatureFlaggable { +struct AppIconSelectionView: View, ThemeApplicable, FeatureFlaggable { private let windowUUID: WindowUUID private let logger: Logger private let telemetry: AppIconSelectionTelemetry @@ -36,7 +36,7 @@ struct AppIconSelectionView: View, ThemeApplicable, LegacyFeatureFlaggable { } var availableAppIcons: [AppIcon] { - guard featureFlags.isFeatureEnabled(.appIconSelection, checking: .buildOnly) + guard featureFlagsProvider.isEnabled(.appIconSelection) else { return AppIcon.allCases.filter({ $0.isFunIcon == false }) } diff --git a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/AddressBarSettingsView.swift b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/AddressBarSettingsView.swift index 21740a7b80cf3..bd74578ea5834 100644 --- a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/AddressBarSettingsView.swift +++ b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/AddressBarSettingsView.swift @@ -7,7 +7,7 @@ import Common import Shared /// The main view displaying the settings for the address bar position menu. -struct AddressBarSettingsView: View, LegacyFeatureFlaggable { +struct AddressBarSettingsView: View, UserFeaturePreferenceProvider { let windowUUID: WindowUUID /// NOTE: To avoid duplication, the old view model is reused in the new address bar setting menu. /// TODO(FXIOS-12000): Once the experiment is done, we can remove the old viewmodel and move it to here. @@ -30,7 +30,7 @@ struct AddressBarSettingsView: View, LegacyFeatureFlaggable { } private var addressBarPosition: SearchBarPosition { - LegacyFeatureFlagsManager.shared.getCustomState(for: .searchBarPosition) ?? .bottom + userPreferences.searchBarPosition } private var viewBackground: Color { diff --git a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift index 92e67264d4046..879af016990c9 100644 --- a/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift +++ b/firefox-ios/Client/Frontend/Settings/AppearanceSettings/StylingViewModifiers.swift @@ -50,7 +50,8 @@ struct ColoredListStyle: ViewModifier { .scrollContentBackground(.hidden) .background(backgroundColor) } else { - content.listStyle(.plain) + content + .listStyle(.plain) } } } diff --git a/firefox-ios/Client/Frontend/Settings/FirefoxSuggestSettingsViewController.swift b/firefox-ios/Client/Frontend/Settings/FirefoxSuggestSettingsViewController.swift index 63d02cde6f1bb..dbfbca3c69c9d 100644 --- a/firefox-ios/Client/Frontend/Settings/FirefoxSuggestSettingsViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/FirefoxSuggestSettingsViewController.swift @@ -7,7 +7,9 @@ import Foundation import UIKit /// A view controller that manages the hidden Firefox Suggest debug settings. -class FirefoxSuggestSettingsViewController: SettingsTableViewController, LegacyFeatureFlaggable { +class FirefoxSuggestSettingsViewController: SettingsTableViewController, + FeatureFlaggable, + UserFeaturePreferenceProvider { init(profile: Profile?, windowUUID: WindowUUID) { super.init(style: .grouped, windowUUID: windowUUID) self.profile = profile @@ -32,7 +34,7 @@ class FirefoxSuggestSettingsViewController: SettingsTableViewController, LegacyF } var sections: [SettingSection] = [SettingSection(title: nil, children: [enabled])] - if featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) { + if featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled { sections.append(SettingSection( title: nil, children: [ diff --git a/firefox-ios/Client/Frontend/Settings/HomepageSettings/HomePageSettingViewController.swift b/firefox-ios/Client/Frontend/Settings/HomepageSettings/HomePageSettingViewController.swift index 15d00e9049824..4749fb6d70756 100644 --- a/firefox-ios/Client/Frontend/Settings/HomepageSettings/HomePageSettingViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/HomepageSettings/HomePageSettingViewController.swift @@ -6,7 +6,9 @@ import Foundation import Shared import Common -class HomePageSettingViewController: SettingsTableViewController, LegacyFeatureFlaggable { +class HomePageSettingViewController: SettingsTableViewController, + LegacyFeatureFlaggable, // TODO: ROUX remove with 15192 + UserFeaturePreferenceProvider { // MARK: - Variables /* variables for checkmark settings */ let prefs: Prefs @@ -132,7 +134,7 @@ class HomePageSettingViewController: SettingsTableViewController, LegacyFeatureF prefs: profile.prefs, theme: themeManager.getCurrentTheme(for: windowUUID), prefKey: PrefsKeys.HomepageSettings.JumpBackInSection, - defaultValue: featureFlags.isFeatureEnabled(.homepageJumpBackinSectionDefault, checking: .userOnly), + defaultValue: userPreferences.isHomepageJumpBackInSectionEnabled, titleText: .Settings.Homepage.CustomizeFirefoxHome.JumpBackIn ) { value in store.dispatch( @@ -149,7 +151,7 @@ class HomePageSettingViewController: SettingsTableViewController, LegacyFeatureF prefs: profile.prefs, theme: themeManager.getCurrentTheme(for: windowUUID), prefKey: PrefsKeys.HomepageSettings.BookmarksSection, - defaultValue: featureFlags.isFeatureEnabled(.homepageBookmarksSectionDefault, checking: .userOnly), + defaultValue: userPreferences.isHomepageBookmarksSectionEnabled, titleText: .Settings.Homepage.CustomizeFirefoxHome.Bookmarks ) { value in store.dispatch( @@ -161,6 +163,26 @@ class HomePageSettingViewController: SettingsTableViewController, LegacyFeatureF ) } sectionItems.append(bookmarksSetting) + + if featureFlags.isFeatureEnabled(.worldCupWidget, checking: .buildOnly) { + let windowUUID = self.windowUUID + let worldCupSetting = BoolSetting( + prefs: profile.prefs, + theme: themeManager.getCurrentTheme(for: windowUUID), + prefKey: PrefsKeys.HomepageSettings.WorldCupSection, + defaultValue: true, + titleText: .Settings.Homepage.CustomizeFirefoxHome.WorldCup + ) { value in + store.dispatch( + WorldCupAction( + windowUUID: windowUUID, + actionType: WorldCupActionType.didChangeHomepageSettings, + shouldShowHomepageWorldCupSection: value + ) + ) + } + sectionItems.append(worldCupSetting) + } } // TODO: FXIOS-12980: Replace "Stories" title with "Top Stories" string once it is translated in v143 @@ -203,7 +225,7 @@ class HomePageSettingViewController: SettingsTableViewController, LegacyFeatureF } private func setupStartAtHomeSection() -> SettingSection { - let pref: StartAtHome = featureFlags.getCustomState(for: .startAtHome) ?? .afterFourHours + let pref = userPreferences.startAtHomeSetting currentStartAtHomeSetting = StartAtHomeSetting(rawValue: pref.rawValue) ?? .afterFourHours typealias a11y = AccessibilityIdentifiers.Settings.Homepage.StartAtHome diff --git a/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesRowCountSettingsController.swift b/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesRowCountSettingsController.swift index 2f35f99616725..6f4ca6b4924f4 100644 --- a/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesRowCountSettingsController.swift +++ b/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesRowCountSettingsController.swift @@ -6,7 +6,7 @@ import Foundation import Common import Shared -class TopSitesRowCountSettingsController: SettingsTableViewController, LegacyFeatureFlaggable { +class TopSitesRowCountSettingsController: SettingsTableViewController, FeatureFlaggable { let prefs: Prefs var numberOfRows: Int32 nonisolated static let defaultNumberOfRows: Int32 = 2 @@ -32,15 +32,23 @@ class TopSitesRowCountSettingsController: SettingsTableViewController, LegacyFea return num == self.numberOfRows }, onChecked: { + guard self.numberOfRows != num else { return } self.numberOfRows = num self.prefs.setInt(Int32(num), forKey: PrefsKeys.NumberOfTopSiteRows) self.tableView.reloadData() - NotificationCenter.default.post(name: .HomePanelPrefsChanged, object: nil) + + store.dispatch( + TopSitesAction( + numberOfRows: Int(num), + windowUUID: self.windowUUID, + actionType: TopSitesActionType.updatedNumberOfRows + ) + ) }) } var rows = [CheckmarkSetting]() - if featureFlags.isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.homepageSearchBar) { rows = [1, 2].map(createSetting) } else { rows = [1, 2, 3, 4].map(createSetting) @@ -55,7 +63,7 @@ class TopSitesRowCountSettingsController: SettingsTableViewController, LegacyFea private func updateNumberofRows() { let defaultValue = TopSitesRowCountSettingsController.defaultNumberOfRows - if featureFlags.isFeatureEnabled(.homepageSearchBar, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.homepageSearchBar) { let savedNumberOfRows = self.prefs.intForKey(PrefsKeys.NumberOfTopSiteRows) ?? defaultValue numberOfRows = savedNumberOfRows > 2 ? defaultValue : savedNumberOfRows } else { diff --git a/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesSettingsViewController.swift b/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesSettingsViewController.swift index 86821283119a6..8257c87ec0dda 100644 --- a/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesSettingsViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/HomepageSettings/TopSitesSettings/TopSitesSettingsViewController.swift @@ -6,7 +6,7 @@ import Foundation import Common import Shared -final class TopSitesSettingsViewController: SettingsTableViewController, LegacyFeatureFlaggable { +final class TopSitesSettingsViewController: SettingsTableViewController, UserFeaturePreferenceProvider { // MARK: - Initializers init(windowUUID: WindowUUID) { super.init(style: .grouped, windowUUID: windowUUID) @@ -51,7 +51,7 @@ final class TopSitesSettingsViewController: SettingsTableViewController, LegacyF prefs: profile.prefs, theme: themeManager.getCurrentTheme(for: windowUUID), prefKey: PrefsKeys.FeatureFlags.SponsoredShortcuts, - defaultValue: featureFlags.isFeatureEnabled(.hntSponsoredShortcuts, checking: .userOnly), + defaultValue: userPreferences.isSponsoredShortcutsEnabled, titleText: .Settings.Homepage.Shortcuts.SponsoredShortcutsToggle ) { _ in store.dispatch( diff --git a/firefox-ios/Client/Frontend/Settings/Main/AppSettingsTableViewController.swift b/firefox-ios/Client/Frontend/Settings/Main/AppSettingsTableViewController.swift index 3a75182908278..a7d0e9d149020 100644 --- a/firefox-ios/Client/Frontend/Settings/Main/AppSettingsTableViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/Main/AppSettingsTableViewController.swift @@ -59,7 +59,7 @@ protocol AppSettingsScreen: UIViewController { /// App Settings Screen (triggered by tapping the 'Gear' in the Tab Tray Controller) class AppSettingsTableViewController: SettingsTableViewController, AppSettingsScreen, - LegacyFeatureFlaggable, + FeatureFlaggable, DebugSettingsDelegate, SearchBarLocationProvider, SharedSettingsDelegate { @@ -379,7 +379,7 @@ class AppSettingsTableViewController: SettingsTableViewController, ThemeSetting(settings: self, settingsDelegate: parentCoordinator) ] - if isSearchBarLocationFeatureEnabled, let profile { + if let profile { generalSettings.append( SearchBarSetting(settings: self, profile: profile, settingsDelegate: parentCoordinator) ) @@ -399,11 +399,11 @@ class AppSettingsTableViewController: SettingsTableViewController, generalSettings.append(SummarizeSetting(settings: self, settingsDelegate: parentCoordinator)) } - if featureFlags.isFeatureEnabled(.translation, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.translation) { generalSettings.append(TranslationSetting(settings: self, settingsDelegate: parentCoordinator)) } - if featureFlags.isFeatureEnabled(.aiKillSwitch, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.aiKillSwitch) { generalSettings.append(AIControlsSetting(settings: self, settingsDelegate: parentCoordinator)) } @@ -461,7 +461,7 @@ class AppSettingsTableViewController: SettingsTableViewController, ] // Only add this toggle to the Settings if Sent from Firefox feature flag is enabled from Nimbus - if featureFlags.isFeatureEnabled(.sentFromFirefox, checking: .buildOnly), let profile { + if featureFlagsProvider.isEnabled(.sentFromFirefox), let profile { supportSettings.append( SentFromFirefoxSetting( prefs: profile.prefs, diff --git a/firefox-ios/Client/Frontend/Settings/Main/Debug/FeatureFlags/FeatureFlagsDebugViewController.swift b/firefox-ios/Client/Frontend/Settings/Main/Debug/FeatureFlags/FeatureFlagsDebugViewController.swift index 45aab26b42b1f..79cdb8596011d 100644 --- a/firefox-ios/Client/Frontend/Settings/Main/Debug/FeatureFlags/FeatureFlagsDebugViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/Main/Debug/FeatureFlags/FeatureFlagsDebugViewController.swift @@ -30,6 +30,13 @@ final class FeatureFlagsDebugViewController: SettingsTableViewController, Legacy private func generateFeatureFlagToggleSettings() -> SettingSection { // For better code readability and parsability in-app, please keep in alphabetical order by title let children: [Setting] = [ + FeatureFlagsBoolSetting( + with: .httpsUpgrade, + titleText: format(string: "Automatic HTTPS upgrade"), + statusText: format(string: "Toggle to enable automatic HTTPS upgrade.") + ) { [weak self] _ in + self?.reloadView() + }, FeatureFlagsBoolSetting( with: .adsClient, titleText: format(string: "Ads Client"), @@ -234,6 +241,13 @@ final class FeatureFlagsDebugViewController: SettingsTableViewController, Legacy ) { [weak self] _ in self?.reloadView() }, + FeatureFlagsBoolSetting( + with: .worldCupWidget, + titleText: format(string: "World Cup Widget"), + statusText: format(string: "Toggle to enable the World Cup widget feature on the Homepage") + ) { [weak self] _ in + self?.reloadView() + }, FeatureFlagsBoolSetting( with: .hostedSummarizer, titleText: format(string: "Hosted Summarizer Feature"), diff --git a/firefox-ios/Client/Frontend/Settings/Main/Debug/ResetTermsOfServiceAcceptancePage.swift b/firefox-ios/Client/Frontend/Settings/Main/Debug/ResetTermsOfServiceAcceptancePage.swift index cdea03c369376..3c603eb0f8b47 100644 --- a/firefox-ios/Client/Frontend/Settings/Main/Debug/ResetTermsOfServiceAcceptancePage.swift +++ b/firefox-ios/Client/Frontend/Settings/Main/Debug/ResetTermsOfServiceAcceptancePage.swift @@ -4,7 +4,7 @@ import Shared -class ResetTermsOfServiceAcceptancePage: HiddenSetting, LegacyFeatureFlaggable { +class ResetTermsOfServiceAcceptancePage: HiddenSetting, FeatureFlaggable { private weak var settingsDelegate: DebugSettingsDelegate? init(settings: SettingsTableViewController, @@ -15,7 +15,7 @@ class ResetTermsOfServiceAcceptancePage: HiddenSetting, LegacyFeatureFlaggable { // Only show this debug setting when Terms Of Service feature is enabled override var hidden: Bool { - return !featureFlags.isFeatureEnabled(.tosFeature, checking: .buildAndUser) + return !featureFlagsProvider.isEnabled(.tosFeature) } override var title: NSAttributedString? { diff --git a/firefox-ios/Client/Frontend/Settings/SearchBar/SearchBarSettingsViewModel.swift b/firefox-ios/Client/Frontend/Settings/SearchBar/SearchBarSettingsViewModel.swift index 7f88ae29cec14..8c8681c345008 100644 --- a/firefox-ios/Client/Frontend/Settings/SearchBar/SearchBarSettingsViewModel.swift +++ b/firefox-ios/Client/Frontend/Settings/SearchBar/SearchBarSettingsViewModel.swift @@ -48,37 +48,25 @@ protocol SearchBarPreferenceDelegate: AnyObject { } /// This protocol provides access to search bar location properties related to `FeatureFlagsManager`. -protocol SearchBarLocationProvider: LegacyFeatureFlaggable { - var isSearchBarLocationFeatureEnabled: Bool { get } +protocol SearchBarLocationProvider: UserFeaturePreferenceProvider { var searchBarPosition: SearchBarPosition { get } @MainActor var isBottomSearchBar: Bool { get } } extension SearchBarLocationProvider { - var isSearchBarLocationFeatureEnabled: Bool { - let isiPad = UIDeviceDetails.userInterfaceIdiom == .pad - let isFeatureEnabled = featureFlags.isFeatureEnabled(.bottomSearchBar, checking: .buildOnly) - - return isFeatureEnabled && !isiPad - } - var searchBarPosition: SearchBarPosition { - guard let position: SearchBarPosition = featureFlags.getCustomState(for: .searchBarPosition) else { - return .bottom - } - - return position + userPreferences.searchBarPosition } var isBottomSearchBar: Bool { - guard isSearchBarLocationFeatureEnabled else { return false } + guard UIDeviceDetails.userInterfaceIdiom != .pad else { return false } return searchBarPosition == .bottom } } -final class SearchBarSettingsViewModel: LegacyFeatureFlaggable { +final class SearchBarSettingsViewModel: FeatureFlaggable, UserFeaturePreferenceProvider { weak var delegate: SearchBarPreferenceDelegate? private let prefs: Prefs @@ -89,7 +77,7 @@ final class SearchBarSettingsViewModel: LegacyFeatureFlaggable { } var isNewAddressBarOn: Bool { - featureFlags.isFeatureEnabled(.addressBarMenu, checking: .buildOnly) + featureFlagsProvider.isEnabled(.addressBarMenu) } var title: String { @@ -101,11 +89,7 @@ final class SearchBarSettingsViewModel: LegacyFeatureFlaggable { } var searchBarPosition: SearchBarPosition { - guard let position: SearchBarPosition = featureFlags.getCustomState(for: .searchBarPosition) else { - return .bottom - } - - return position + userPreferences.searchBarPosition } // TODO: FXIOS-12830 view models should not contain Views that require main actor isolation @@ -133,9 +117,9 @@ final class SearchBarSettingsViewModel: LegacyFeatureFlaggable { extension SearchBarSettingsViewModel { @MainActor func saveSearchBarPosition(_ searchBarPosition: SearchBarPosition) { - let previousPosition: SearchBarPosition? = featureFlags.getCustomState(for: .searchBarPosition) + let previousPosition = userPreferences.searchBarPosition - featureFlags.set(feature: .searchBarPosition, to: searchBarPosition) + userPreferences.setSearchBarPosition(searchBarPosition) delegate?.didUpdateSearchBarPositionPreference() recordPreferenceChange(searchBarPosition, previousPosition: previousPosition) diff --git a/firefox-ios/Client/Frontend/Settings/SearchEnginePicker.swift b/firefox-ios/Client/Frontend/Settings/SearchEnginePicker.swift index 71e513aabba31..a5a364c1a4cbc 100644 --- a/firefox-ios/Client/Frontend/Settings/SearchEnginePicker.swift +++ b/firefox-ios/Client/Frontend/Settings/SearchEnginePicker.swift @@ -7,7 +7,7 @@ import UIKit class SearchEnginePicker: ThemedTableViewController { weak var delegate: SearchEnginePickerDelegate? var engines: [OpenSearchEngine] = [] - var selectedSearchEngineName: String? + var selectedSearchEngineID: String? override func viewDidLoad() { super.viewDidLoad() @@ -33,7 +33,7 @@ class SearchEnginePicker: ThemedTableViewController { let size = CGSize(width: OpenSearchEngine.UX.preferredIconSize, height: OpenSearchEngine.UX.preferredIconSize) cell.imageView?.image = engine.image.createScaled(size) - if engine.shortName == selectedSearchEngineName { + if engine.engineID == selectedSearchEngineID { cell.accessoryType = .checkmark } return cell diff --git a/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift b/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift index ed415d7b99a68..ed4a6e09bac7b 100644 --- a/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift @@ -15,7 +15,9 @@ protocol SearchEnginePickerDelegate: AnyObject { ) } -final class SearchSettingsTableViewController: ThemedTableViewController, LegacyFeatureFlaggable { +final class SearchSettingsTableViewController: ThemedTableViewController, + FeatureFlaggable, + UserFeaturePreferenceProvider { private struct UX { static let imageViewCornerRadius: CGFloat = 4 static let textLabelMinimumScaleFactor: CGFloat = 0.5 @@ -65,11 +67,11 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Legacy // MARK: - Pre Search Section var isTrendingSearchesEnabled: Bool { - return featureFlags.isFeatureEnabled(.trendingSearches, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.trendingSearches) } var isRecentSearchesEnabled: Bool { - return featureFlags.isFeatureEnabled(.recentSearches, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.recentSearches) } // Determines how to display the pre search settings based on the feature flags @@ -411,7 +413,7 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Legacy } private func configureCellForNonSponsoredAction(cell: ThemedSubtitleTableViewCell) { - if featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) { + if featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled { buildSettingWith( prefKey: PrefsKeys.SearchSettings.showFirefoxNonSponsoredSuggestions, defaultValue: model.shouldShowFirefoxSuggestions, @@ -429,7 +431,7 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Legacy } private func configureCellForSponsoredAction(cell: ThemedSubtitleTableViewCell) { - if featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) { + if featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled { buildSettingWith( prefKey: PrefsKeys.SearchSettings.showFirefoxSponsoredSuggestions, defaultValue: model.shouldShowSponsoredSuggestions, @@ -488,7 +490,7 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Legacy case .searchEnginesSuggestions: return SearchSuggestItem.allCases.count case .firefoxSuggestSettings: - return featureFlags.isFeatureEnabled(.firefoxSuggestFeature, checking: .buildAndUser) + return featureFlagsProvider.isEnabled(.firefoxSuggestFeature) && userPreferences.isFirefoxSuggestEnabled ? FirefoxSuggestItem.allCases.count : 3 } } @@ -504,7 +506,7 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Legacy // Every engine is a valid choice for the default engine, even the current default engine. searchEnginePicker.engines = model.orderedEngines.sorted { e, f in e.shortName < f.shortName } searchEnginePicker.delegate = self - searchEnginePicker.selectedSearchEngineName = model.defaultEngine?.shortName + searchEnginePicker.selectedSearchEngineID = model.defaultEngine?.engineID navigationController?.pushViewController(searchEnginePicker, animated: true) case .alternateEngines: let isLastItem = indexPath.item + 1 == model.orderedEngines.count diff --git a/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeMiddleware.swift b/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeMiddleware.swift index bf72a233ba2f6..1ef4f3d78209d 100644 --- a/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeMiddleware.swift +++ b/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeMiddleware.swift @@ -94,7 +94,7 @@ final class ThemeManagerMiddleware: ThemeManagerProvider { func getCurrentThemeManagerState(windowUUID: WindowUUID) -> ThemeSettingsState { ThemeSettingsState(windowUUID: windowUUID, useSystemAppearance: themeManager.systemThemeIsOn, - isAutomaticBrightnessEnable: themeManager.automaticBrightnessIsOn, + isAutomaticBrightnessEnabled: themeManager.automaticBrightnessIsOn, manualThemeSelected: themeManager.getUserManualTheme(), userBrightnessThreshold: themeManager.automaticBrightnessValue, systemBrightness: getScreenBrightness()) diff --git a/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeSettingsState.swift b/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeSettingsState.swift index 2e18b8ac8a373..988a31360665f 100644 --- a/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeSettingsState.swift +++ b/firefox-ios/Client/Frontend/Settings/ThemeSettings/ThemeSettingsState.swift @@ -3,15 +3,17 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/ import Common +import CopyWithUpdates import Redux +@CopyWithUpdates struct ThemeSettingsState: ScreenState { + var windowUUID: WindowUUID var useSystemAppearance: Bool var isAutomaticBrightnessEnabled: Bool var manualThemeSelected: ThemeType var userBrightnessThreshold: Float var systemBrightness: Float - var windowUUID: WindowUUID init(appState: AppState, uuid: WindowUUID) { guard let themeState = appState.componentState( @@ -25,7 +27,7 @@ struct ThemeSettingsState: ScreenState { self.init(windowUUID: themeState.windowUUID, useSystemAppearance: themeState.useSystemAppearance, - isAutomaticBrightnessEnable: themeState.isAutomaticBrightnessEnabled, + isAutomaticBrightnessEnabled: themeState.isAutomaticBrightnessEnabled, manualThemeSelected: themeState.manualThemeSelected, userBrightnessThreshold: themeState.userBrightnessThreshold, systemBrightness: themeState.systemBrightness) @@ -34,7 +36,7 @@ struct ThemeSettingsState: ScreenState { init(windowUUID: WindowUUID) { self.init(windowUUID: windowUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: ThemeType.light, userBrightnessThreshold: 0, systemBrightness: 1) @@ -42,13 +44,13 @@ struct ThemeSettingsState: ScreenState { init(windowUUID: WindowUUID, useSystemAppearance: Bool, - isAutomaticBrightnessEnable: Bool, + isAutomaticBrightnessEnabled: Bool, manualThemeSelected: ThemeType, userBrightnessThreshold: Float, systemBrightness: Float) { self.windowUUID = windowUUID self.useSystemAppearance = useSystemAppearance - self.isAutomaticBrightnessEnabled = isAutomaticBrightnessEnable + self.isAutomaticBrightnessEnabled = isAutomaticBrightnessEnabled self.manualThemeSelected = manualThemeSelected self.userBrightnessThreshold = userBrightnessThreshold self.systemBrightness = systemBrightness @@ -83,62 +85,32 @@ struct ThemeSettingsState: ScreenState { } static func defaultState(from state: ThemeSettingsState) -> ThemeSettingsState { - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: state.useSystemAppearance, - isAutomaticBrightnessEnable: state.isAutomaticBrightnessEnabled, - manualThemeSelected: state.manualThemeSelected, - userBrightnessThreshold: state.userBrightnessThreshold, - systemBrightness: state.systemBrightness) + return state.copyWithUpdates() } private static func handleSystemThemeChangedAction(state: Self, action: ThemeSettingsMiddlewareAction) -> Self { let useSystemAppearance = action.themeSettingsState?.useSystemAppearance ?? state.useSystemAppearance - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: useSystemAppearance, - isAutomaticBrightnessEnable: state.isAutomaticBrightnessEnabled, - manualThemeSelected: state.manualThemeSelected, - userBrightnessThreshold: state.userBrightnessThreshold, - systemBrightness: state.systemBrightness) + return state.copyWithUpdates(useSystemAppearance: useSystemAppearance) } private static func handleAutomaticBrightnessChangedAction(state: Self, action: ThemeSettingsMiddlewareAction) -> Self { let enabled = action.themeSettingsState?.isAutomaticBrightnessEnabled ?? state.isAutomaticBrightnessEnabled - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: state.useSystemAppearance, - isAutomaticBrightnessEnable: enabled, - manualThemeSelected: state.manualThemeSelected, - userBrightnessThreshold: state.userBrightnessThreshold, - systemBrightness: state.systemBrightness) + return state.copyWithUpdates(isAutomaticBrightnessEnabled: enabled) } private static func handleManualThemeChangedAction(state: Self, action: ThemeSettingsMiddlewareAction) -> Self { let theme = action.themeSettingsState?.manualThemeSelected ?? state.manualThemeSelected - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: state.useSystemAppearance, - isAutomaticBrightnessEnable: state.isAutomaticBrightnessEnabled, - manualThemeSelected: theme, - userBrightnessThreshold: state.userBrightnessThreshold, - systemBrightness: state.systemBrightness) + return state.copyWithUpdates(manualThemeSelected: theme) } private static func handleUserBrightnessChangedAction(state: Self, action: ThemeSettingsMiddlewareAction) -> Self { let brightnessValue = action.themeSettingsState?.userBrightnessThreshold ?? state.userBrightnessThreshold - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: state.useSystemAppearance, - isAutomaticBrightnessEnable: state.isAutomaticBrightnessEnabled, - manualThemeSelected: state.manualThemeSelected, - userBrightnessThreshold: brightnessValue, - systemBrightness: state.systemBrightness) + return state.copyWithUpdates(userBrightnessThreshold: brightnessValue) } private static func handleSystemBrightnessChangedAction(state: Self, action: ThemeSettingsMiddlewareAction) -> Self { let brightnessValue = action.themeSettingsState?.systemBrightness ?? state.systemBrightness - return ThemeSettingsState(windowUUID: state.windowUUID, - useSystemAppearance: state.useSystemAppearance, - isAutomaticBrightnessEnable: state.isAutomaticBrightnessEnabled, - manualThemeSelected: state.manualThemeSelected, - userBrightnessThreshold: state.userBrightnessThreshold, - systemBrightness: brightnessValue) + return state.copyWithUpdates(systemBrightness: brightnessValue) } static func == (lhs: ThemeSettingsState, rhs: ThemeSettingsState) -> Bool { diff --git a/firefox-ios/Client/Frontend/Settings/Translation/TranslationPickerSettingsViewController.swift b/firefox-ios/Client/Frontend/Settings/Translation/TranslationPickerSettingsViewController.swift index ef7fbe2049d36..65dffea85b7ad 100644 --- a/firefox-ios/Client/Frontend/Settings/Translation/TranslationPickerSettingsViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/Translation/TranslationPickerSettingsViewController.swift @@ -303,6 +303,10 @@ final class TranslationPickerSettingsViewController: UIViewController, // MARK: - Toggle actions @objc private func didToggleTranslations(_ sender: UISwitch) { + guard !state.isEditing else { + sender.setOn(state.isTranslationsEnabled, animated: false) + return + } store.dispatch(TranslationSettingsViewAction( windowUUID: windowUUID, actionType: TranslationSettingsViewActionType.toggleTranslationsEnabled @@ -310,6 +314,10 @@ final class TranslationPickerSettingsViewController: UIViewController, } @objc private func didToggleAutoTranslate(_ sender: UISwitch) { + guard !state.isEditing else { + sender.setOn(state.isAutoTranslateEnabled, animated: false) + return + } store.dispatch(TranslationSettingsViewAction( windowUUID: windowUUID, actionType: TranslationSettingsViewActionType.toggleAutoTranslate diff --git a/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsAction.swift b/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsAction.swift index ab1b8875746b7..2fe00b7c5c151 100644 --- a/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsAction.swift +++ b/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsAction.swift @@ -69,5 +69,4 @@ enum TranslationSettingsViewActionType: ActionType { enum TranslationSettingsMiddlewareActionType: ActionType { case didLoadSettings case didUpdateSettings - case didResetStorage } diff --git a/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsMiddleware.swift b/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsMiddleware.swift index 77b057d119b65..8ddad4d8acec6 100644 --- a/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsMiddleware.swift +++ b/firefox-ios/Client/Frontend/Settings/Translation/TranslationSettingsMiddleware.swift @@ -12,6 +12,8 @@ final class TranslationSettingsMiddleware { private let manager: PreferredTranslationLanguagesManager private let modelsFetcher: TranslationModelsFetcherProtocol private let localeProvider: LocaleProvider + private var resetStorageTask: Task? + private var loadSettingsTask: Task? private var isAutoTranslateEnabled: Bool { prefs.boolForKey(PrefsKeys.Settings.translationAutoTranslate) ?? false @@ -27,6 +29,11 @@ final class TranslationSettingsMiddleware { self.localeProvider = localeProvider } + deinit { + resetStorageTask?.cancel() + loadSettingsTask?.cancel() + } + lazy var translationSettingsProvider: Middleware = { state, action in guard let action = action as? TranslationSettingsViewAction else { return } self.handleAction(action, state: state) @@ -49,7 +56,10 @@ final class TranslationSettingsMiddleware { windowUUID: action.windowUUID, actionType: TranslationSettingsMiddlewareActionType.didLoadSettings )) - Task { await self.loadSettings(windowUUID: action.windowUUID) } + loadSettingsTask?.cancel() + loadSettingsTask = Task { [weak self] in + await self?.loadSettings(windowUUID: action.windowUUID) + } case TranslationSettingsViewActionType.toggleTranslationsEnabled: let current = prefs.boolForKey(PrefsKeys.Settings.translationsFeature) ?? true @@ -72,13 +82,9 @@ final class TranslationSettingsMiddleware { actionType: TranslationSettingsMiddlewareActionType.didUpdateSettings )) if !newValue { - Task { + resetStorageTask?.cancel() + resetStorageTask = Task { [modelsFetcher] in await modelsFetcher.resetStorage() - store.dispatch(TranslationSettingsMiddlewareAction( - isTranslationsEnabled: newValue, - windowUUID: action.windowUUID, - actionType: TranslationSettingsMiddlewareActionType.didResetStorage - )) } } case TranslationSettingsViewActionType.toggleAutoTranslate: diff --git a/firefox-ios/Client/Frontend/Share/ShareTelemetryActivityItemProvider.swift b/firefox-ios/Client/Frontend/Share/ShareTelemetryActivityItemProvider.swift index 05e77f049d6da..dbc487accc662 100644 --- a/firefox-ios/Client/Frontend/Share/ShareTelemetryActivityItemProvider.swift +++ b/firefox-ios/Client/Frontend/Share/ShareTelemetryActivityItemProvider.swift @@ -7,19 +7,22 @@ import UniformTypeIdentifiers /// A special `UIActivityItemProvider` which never shares additional content, but instead records telemetry based on the /// share activity chosen. -class ShareTelemetryActivityItemProvider: UIActivityItemProvider, @unchecked Sendable, LegacyFeatureFlaggable { +class ShareTelemetryActivityItemProvider: UIActivityItemProvider, + @unchecked Sendable, + FeatureFlaggable, + UserFeaturePreferenceProvider { private let shareTypeName: String private let shareMessage: ShareMessage? private let telemetry: ShareTelemetry // FXIOS-9879 For the Sent from Firefox experiment private var isEnrolledInSentFromFirefox: Bool { - return featureFlags.isFeatureEnabled(.sentFromFirefox, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.sentFromFirefox) } // FXIOS-9879 For the Sent from Firefox experiment private var isOptedInSentFromFirefox: Bool { - return featureFlags.isFeatureEnabled(.sentFromFirefox, checking: .userOnly) + return userPreferences.isSentFromFirefoxEnabled } init( diff --git a/firefox-ios/Client/Frontend/Share/URLActivityItemProvider.swift b/firefox-ios/Client/Frontend/Share/URLActivityItemProvider.swift index e8af1e8b5358c..ef21aefa895b4 100644 --- a/firefox-ios/Client/Frontend/Share/URLActivityItemProvider.swift +++ b/firefox-ios/Client/Frontend/Share/URLActivityItemProvider.swift @@ -5,6 +5,7 @@ import Foundation import UniformTypeIdentifiers import Shared +import Common class URLActivityItemProvider: UIActivityItemProvider, @unchecked Sendable { private struct ActivityIdentifiers { @@ -12,7 +13,7 @@ class URLActivityItemProvider: UIActivityItemProvider, @unchecked Sendable { } private static var isSentFromFirefoxTreatmentA: Bool { - return LegacyFeatureFlagsManager.shared.isFeatureEnabled(.sentFromFirefoxTreatmentA, checking: .buildOnly) + return (AppContainer.shared.resolve() as FeatureFlagProviding).isEnabled(.sentFromFirefoxTreatmentA) } private let url: URL diff --git a/firefox-ios/Client/Frontend/Summarizer/SummarizerNimbusUtils.swift b/firefox-ios/Client/Frontend/Summarizer/SummarizerNimbusUtils.swift index 327c1fb32d740..89c1b30a646e7 100644 --- a/firefox-ios/Client/Frontend/Summarizer/SummarizerNimbusUtils.swift +++ b/firefox-ios/Client/Frontend/Summarizer/SummarizerNimbusUtils.swift @@ -35,7 +35,7 @@ extension SummarizerNimbusUtils { } /// Tiny utility to simplify checking for availability of the summarizers -struct DefaultSummarizerNimbusUtils: LegacyFeatureFlaggable, SummarizerNimbusUtils { +struct DefaultSummarizerNimbusUtils: FeatureFlaggable, SummarizerNimbusUtils { private let prefs: Prefs private let localeProvider: LocaleProvider private let appleIntelligenceUtil: AppleIntelligenceUtil @@ -55,7 +55,7 @@ struct DefaultSummarizerNimbusUtils: LegacyFeatureFlaggable, SummarizerNimbusUti } var isLanguageExpansionEnabled: Bool { - return featureFlags.isFeatureEnabled(.summarizerLanguageExpansion, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.summarizerLanguageExpansion) } /// Takes into consideration that summarize feature is on, @@ -101,15 +101,15 @@ struct DefaultSummarizerNimbusUtils: LegacyFeatureFlaggable, SummarizerNimbusUti } func isHostedSummarizerEnabled() -> Bool { - return featureFlags.isFeatureEnabled(.hostedSummarizer, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.hostedSummarizer) } func isAppAttestAuthEnabled() -> Bool { - return featureFlags.isFeatureEnabled(.summarizerAppAttestAuth, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.summarizerAppAttestAuth) } func usesPermissiveGuardrails() -> Bool { - return featureFlags.isFeatureEnabled(.summarizerPermissiveGuardrails, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.summarizerPermissiveGuardrails) } private func isAppleSummarizerToolbarEndpointEnabled() -> Bool { @@ -117,7 +117,7 @@ struct DefaultSummarizerNimbusUtils: LegacyFeatureFlaggable, SummarizerNimbusUti } private func isHostedSummarizerToolbarEndpointEnabled() -> Bool { - let isFlagEnabled = featureFlags.isFeatureEnabled(.hostedSummarizerToolbarEntrypoint, checking: .buildOnly) + let isFlagEnabled = featureFlagsProvider.isEnabled(.hostedSummarizerToolbarEntrypoint) return isHostedSummarizerEnabled() && isFlagEnabled } @@ -126,7 +126,7 @@ struct DefaultSummarizerNimbusUtils: LegacyFeatureFlaggable, SummarizerNimbusUti } private func isHostedSummarizerShakeGestureEnabled() -> Bool { - let isShakeEnabled = featureFlags.isFeatureEnabled(.hostedSummarizerShakeGesture, checking: .buildOnly) + let isShakeEnabled = featureFlagsProvider.isEnabled(.hostedSummarizerShakeGesture) return isHostedSummarizerEnabled() && isShakeEnabled } diff --git a/firefox-ios/Client/Frontend/Translations/AutoTranslatePromptView.swift b/firefox-ios/Client/Frontend/Translations/AutoTranslatePromptView.swift index e28f91e24135a..00e054659e639 100644 --- a/firefox-ios/Client/Frontend/Translations/AutoTranslatePromptView.swift +++ b/firefox-ios/Client/Frontend/Translations/AutoTranslatePromptView.swift @@ -41,6 +41,8 @@ final class AutoTranslatePromptView: UIView, ThemeApplicable, Notifiable { button.accessibilityLabel = .Translations.AutoTranslatePrompt.EnableButton button.accessibilityIdentifier = AccessibilityIdentifiers.Translations.AutoTranslatePrompt.enableButton button.addTarget(self, action: #selector(self.didTapEnable), for: .touchUpInside) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .vertical) } private lazy var closeButton: CloseButton = .build { button in @@ -97,8 +99,14 @@ final class AutoTranslatePromptView: UIView, ThemeApplicable, Notifiable { contentRow.topAnchor.constraint(equalTo: topBorderView.bottomAnchor, constant: UX.contentPadding.top), contentRow.bottomAnchor.constraint(equalTo: bottomAnchor, constant: UX.contentPadding.bottom), - contentRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: UX.contentPadding.leading), - contentRow.trailingAnchor.constraint(equalTo: trailingAnchor, constant: UX.contentPadding.trailing), + contentRow.leadingAnchor.constraint( + equalTo: safeAreaLayoutGuide.leadingAnchor, + constant: UX.contentPadding.leading + ), + contentRow.trailingAnchor.constraint( + equalTo: safeAreaLayoutGuide.trailingAnchor, + constant: UX.contentPadding.trailing + ), ]) adjustLayout() diff --git a/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift b/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift index 1a5aca117ee7d..4e2efa3dae2fd 100644 --- a/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift +++ b/firefox-ios/Client/Frontend/Translations/TranslationsMiddleware.swift @@ -8,7 +8,7 @@ import Common import Shared @MainActor -final class TranslationsMiddleware: LegacyFeatureFlaggable { +final class TranslationsMiddleware: FeatureFlaggable { private let profile: Profile private let logger: Logger private let windowManager: WindowManager @@ -91,7 +91,6 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { private func handleTappingOnTranslateButton(for action: ToolbarMiddlewareAction, and state: AppState) { guard let gestureType = action.gestureType, let type = action.buttonType, - gestureType == .tap, type == .translate else { return } @@ -112,8 +111,22 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { return } + if gestureType == .longPress { + guard translationConfiguration.state == .active, + featureFlagsProvider.isEnabled(.translationLanguagePicker) + else { return } + showLanguagePickerForActiveTranslation( + for: action, + translatedToLanguage: translationConfiguration.translatedToLanguage, + sourceLanguage: translationConfiguration.sourceLanguage + ) + return + } + + guard gestureType == .tap else { return } + if translationConfiguration.state == .inactive, - featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly) { + featureFlagsProvider.isEnabled(.translationLanguagePicker) { let capturedButton = action.buttonTapped Task { let manager = PreferredTranslationLanguagesManager(prefs: profile.prefs) @@ -160,6 +173,28 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { } } + private func showLanguagePickerForActiveTranslation( + for action: ToolbarMiddlewareAction, + translatedToLanguage: String?, + sourceLanguage: String? + ) { + let capturedButton = action.buttonTapped + Task { + let manager = PreferredTranslationLanguagesManager(prefs: profile.prefs) + let supported = await translationsService.fetchSupportedTargetLanguages() + let languages = manager.preferredLanguages(supportedTargetLanguages: supported) + let filteredLanguages = languages.filter { $0 != sourceLanguage && $0 != translatedToLanguage } + store.dispatch(GeneralBrowserAction( + buttonTapped: capturedButton, + translationLanguages: filteredLanguages, + isPageTranslated: true, + translatedToLanguage: translatedToLanguage, + windowUUID: action.windowUUID, + actionType: GeneralBrowserActionType.showTranslationLanguagePicker + )) + } + } + private func handleTappingRetryButtonOnToast(for action: TranslationsAction, and state: AppState) { guard let language = selectedTargetLanguages[action.windowUUID] else { logger.log( @@ -240,7 +275,7 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { /// When the language picker flag is ON, returns the user's full preferred list. /// When OFF, returns only the primary device language (preserving legacy behavior). private func targetLanguagesForEligibilityCheck() async -> [String] { - if featureFlags.isFeatureEnabled(.translationLanguagePicker, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.translationLanguagePicker) { let supported = await translationsService.fetchSupportedTargetLanguages() return manager.preferredLanguages(supportedTargetLanguages: supported) } @@ -318,10 +353,12 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { private func performTranslation(for action: Action, targetLanguage: String, isPrivate: Bool, autoTranslate: Bool) async { do { + var detectedSourceLanguage: String? try await translationsService.translateCurrentPage( for: action.windowUUID, to: targetLanguage, onLanguageIdentified: { identifiedLanguage, deviceLanguage in + detectedSourceLanguage = identifiedLanguage self.translationsTelemetry.pageLanguageIdentified( identifiedLanguage: identifiedLanguage, deviceLanguage: deviceLanguage @@ -340,6 +377,7 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { for: ToolbarActionType.translationCompleted, with: .active, translatedToLanguage: targetLanguage, + sourceLanguage: detectedSourceLanguage, and: action.windowUUID ) maybeShowAutoTranslatePrompt(windowUUID: action.windowUUID) @@ -386,13 +424,15 @@ final class TranslationsMiddleware: LegacyFeatureFlaggable { for actionType: ToolbarActionType, with state: TranslationConfiguration.IconState, translatedToLanguage: String? = nil, + sourceLanguage: String? = nil, and windowUUID: WindowUUID ) { let toolbarAction = ToolbarAction( translationConfiguration: TranslationConfiguration( prefs: profile.prefs, state: state, - translatedToLanguage: translatedToLanguage + translatedToLanguage: translatedToLanguage, + sourceLanguage: sourceLanguage ), windowUUID: windowUUID, actionType: actionType diff --git a/firefox-ios/Client/Glean/gleanProbes.xcfilelist b/firefox-ios/Client/Glean/gleanProbes.xcfilelist index e7f155a04f7a9..fc1e1029fe9f1 100644 --- a/firefox-ios/Client/Glean/gleanProbes.xcfilelist +++ b/firefox-ios/Client/Glean/gleanProbes.xcfilelist @@ -23,7 +23,6 @@ $(PROJECT_DIR)/Client/Glean/probes/share_sheet.yaml $(PROJECT_DIR)/Client/Glean/probes/share.open_in_firefox_extension.yaml $(PROJECT_DIR)/Client/Glean/probes/tabs_panel.yaml $(PROJECT_DIR)/Client/Glean/probes/terms_of_use.yaml -$(PROJECT_DIR)/Client/Glean/probes/toast.yaml $(PROJECT_DIR)/Client/Glean/probes/toolbar.yaml $(PROJECT_DIR)/Client/Glean/probes/tracking_protection.yaml $(PROJECT_DIR)/Client/Glean/probes/user.yaml diff --git a/firefox-ios/Client/Glean/glean_index.yaml b/firefox-ios/Client/Glean/glean_index.yaml index 8c8c48dbbc015..6862829bb049c 100644 --- a/firefox-ios/Client/Glean/glean_index.yaml +++ b/firefox-ios/Client/Glean/glean_index.yaml @@ -30,7 +30,6 @@ metrics_files: - firefox-ios/Client/Glean/probes/share.open_in_firefox_extension.yaml - firefox-ios/Client/Glean/probes/tabs_panel.yaml - firefox-ios/Client/Glean/probes/terms_of_use.yaml - - firefox-ios/Client/Glean/probes/toast.yaml - firefox-ios/Client/Glean/probes/toolbar.yaml - firefox-ios/Client/Glean/probes/tracking_protection.yaml - firefox-ios/Client/Glean/probes/user.yaml diff --git a/firefox-ios/Client/Glean/probes/toast.yaml b/firefox-ios/Client/Glean/probes/toast.yaml deleted file mode 100644 index 53a8d4d6e97e6..0000000000000 --- a/firefox-ios/Client/Glean/probes/toast.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# 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/. - -# This file defines the metrics that are recorded by the Glean SDK. They are -# automatically converted to Swift code at build time using the `glean_parser` -# PyPI package. - -# This file is organized (roughly) alphabetically by metric names -# for easy navigation - ---- -$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 - -$tags: - - Toasts - -############################################################################### -# Documentation -############################################################################### - -# Add your new metrics and/or events here. - -# Toasts -toasts.close_single_tab: - undo_tapped: - type: event - description: | - Records when the user selects undo after closing a tab. - bugs: - - https://mozilla-hub.atlassian.net/browse/FXIOS-11714 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/25569 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: never - metadata: - tags: - - TabsPanel - -toasts.close_all_tabs: - undo_tapped: - type: event - description: | - Records when the user selects undo after closing all tabs. - bugs: - - https://mozilla-hub.atlassian.net/browse/FXIOS-11714 - data_reviews: - - https://github.com/mozilla-mobile/firefox-ios/pull/25569 - notification_emails: - - fx-ios-data-stewards@mozilla.com - expires: never - metadata: - tags: - - TabsPanel diff --git a/firefox-ios/Client/IntroScreenManager.swift b/firefox-ios/Client/IntroScreenManager.swift index be89dfa38d987..dc23fbc0e4441 100644 --- a/firefox-ios/Client/IntroScreenManager.swift +++ b/firefox-ios/Client/IntroScreenManager.swift @@ -15,7 +15,7 @@ protocol IntroScreenManagerProtocol { func didSeeIntroScreen() } -struct IntroScreenManager: LegacyFeatureFlaggable, IntroScreenManagerProtocol { +struct IntroScreenManager: FeatureFlaggable, IntroScreenManagerProtocol { var prefs: Prefs var shouldShowIntroScreen: Bool { @@ -27,19 +27,19 @@ struct IntroScreenManager: LegacyFeatureFlaggable, IntroScreenManagerProtocol { } var isModernOnboardingEnabled: Bool { - featureFlags.isFeatureEnabled(.modernOnboardingUI, checking: .buildAndUser) + featureFlagsProvider.isEnabled(.modernOnboardingUI) } var shouldShowVideoIntro: Bool { - featureFlags.isFeatureEnabled(.videoIntroOnboarding, checking: .buildOnly) + featureFlagsProvider.isEnabled(.videoIntroOnboarding) } var shouldUseBrandRefreshConfiguration: Bool { - featureFlags.isFeatureEnabled(.shouldUseBrandRefreshConfiguration, checking: .buildAndUser) + featureFlagsProvider.isEnabled(.shouldUseBrandRefreshConfiguration) } var shouldUseJapanConfiguration: Bool { - featureFlags.isFeatureEnabled(.shouldUseJapanConfiguration, checking: .buildAndUser) + featureFlagsProvider.isEnabled(.shouldUseJapanConfiguration) } /// Determines the onboarding variant based on feature flags. diff --git a/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift b/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift index 0d798a53d6476..2462a5db3c62f 100644 --- a/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift +++ b/firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift @@ -31,9 +31,6 @@ final class NimbusFeatureFlagLayer: Sendable { case .addressBarMenu: return checkAddressBarMenuFeature(from: nimbus) - case .bottomSearchBar: - return checkAwesomeBarFeature(for: featureID, from: nimbus) - case .bookmarksSearchFeature: return checkBookmarksSearchFeature(from: nimbus) @@ -112,6 +109,9 @@ final class NimbusFeatureFlagLayer: Sendable { case .hostedSummarizer: return checkHostedSummarizerFeature(from: nimbus) + case .httpsUpgrade: + return checkHttpsUpgradeFeature(from: nimbus) + case .improvedAppStoreReviewTriggerFeature: return checkImprovedAppStoreReviewTriggerFeature(from: nimbus) @@ -174,6 +174,9 @@ final class NimbusFeatureFlagLayer: Sendable { case .quickAnswers: return checkQuickAnswersFeature(from: nimbus) + + case .worldCupWidget: + return checkWorldCupWidgetFeature(from: nimbus) } } @@ -199,17 +202,6 @@ final class NimbusFeatureFlagLayer: Sendable { return config.isTreatmentA } - private func checkAwesomeBarFeature(for featureID: FeatureFlagID, - from nimbus: FxNimbus - ) -> Bool { - let config = nimbus.features.search.value().awesomeBar - - switch featureID { - case .bottomSearchBar: return config.position.isPositionFeatureEnabled - default: return false - } - } - private func checkHNTSponsoredShortcutsFeature(from nimbus: FxNimbus) -> Bool { return nimbus.features.hntSponsoredShortcutsFeature.value().enabled } @@ -403,6 +395,10 @@ final class NimbusFeatureFlagLayer: Sendable { return nimbus.features.hostedSummarizerFeature.value().shakeGesture } + private func checkHttpsUpgradeFeature(from nimbus: FxNimbus) -> Bool { + return nimbus.features.httpsUpgradeFeature.value().enabled + } + private func checkSummarizerAppAttestAuthFeature(from nimbus: FxNimbus) -> Bool { return nimbus.features.summarizerAppAttestAuthFeature.value().enabled } @@ -442,4 +438,8 @@ final class NimbusFeatureFlagLayer: Sendable { private func checkBookmarksSearchFeature(from nimbus: FxNimbus) -> Bool { return nimbus.features.bookmarksSearchFeature.value().enabled } + + private func checkWorldCupWidgetFeature(from nimbus: FxNimbus) -> Bool { + return nimbus.features.worldCupWidgetFeature.value().enabled + } } diff --git a/firefox-ios/Client/Nimbus/NimbusManager.swift b/firefox-ios/Client/Nimbus/NimbusManager.swift index 0333e80eb74bb..7eef88a56a586 100644 --- a/firefox-ios/Client/Nimbus/NimbusManager.swift +++ b/firefox-ios/Client/Nimbus/NimbusManager.swift @@ -12,14 +12,6 @@ extension HasNimbusFeatureFlagLayer { } } -protocol HasNimbusSearchBar { } - -extension HasNimbusSearchBar { - var nimbusSearchBar: NimbusSearchBarLayer { - return NimbusManager.shared.bottomSearchBarLayer - } -} - final class NimbusManager: Sendable { // MARK: - Singleton @@ -29,12 +21,8 @@ final class NimbusManager: Sendable { // MARK: - Properties let featureFlagLayer: NimbusFeatureFlagLayer - let bottomSearchBarLayer: NimbusSearchBarLayer - init(with featureFlagLayer: NimbusFeatureFlagLayer = NimbusFeatureFlagLayer(), - bottomSearchBarLayer: NimbusSearchBarLayer = NimbusSearchBarLayer() - ) { + init(with featureFlagLayer: NimbusFeatureFlagLayer = NimbusFeatureFlagLayer()) { self.featureFlagLayer = featureFlagLayer - self.bottomSearchBarLayer = bottomSearchBarLayer } } diff --git a/firefox-ios/Client/Nimbus/NimbusSearchBarLayer.swift b/firefox-ios/Client/Nimbus/NimbusSearchBarLayer.swift deleted file mode 100644 index ef85a59a4b823..0000000000000 --- a/firefox-ios/Client/Nimbus/NimbusSearchBarLayer.swift +++ /dev/null @@ -1,13 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// 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 Foundation - -final class NimbusSearchBarLayer: Sendable { - // MARK: - Public methods - public func getDefaultPosition(from nimbus: FxNimbus = FxNimbus.shared) -> SearchBarPosition { - let isAtBottom = nimbus.features.search.value().awesomeBar.position.isBottom - return isAtBottom ? .bottom : .top - } -} diff --git a/firefox-ios/Client/TabManagement/Tab.swift b/firefox-ios/Client/TabManagement/Tab.swift index 09e813b21b7c6..39da498a21ab0 100644 --- a/firefox-ios/Client/TabManagement/Tab.swift +++ b/firefox-ios/Client/TabManagement/Tab.swift @@ -86,7 +86,7 @@ typealias TabUUID = String @MainActor class Tab: NSObject, ThemeApplicable, - LegacyFeatureFlaggable, + FeatureFlaggable, ShareTab, ContentBlockerTab, TabWebViewDelegate, @@ -518,6 +518,14 @@ class Tab: NSObject, // Ensures we inject scripts into a new content controller requiredConfiguration.userContentController = .init() self.configuration = requiredConfiguration + + if #available(iOS 18.2, *), + featureFlagsProvider.isEnabled(.httpsUpgrade) { + let pagePrefs = requiredConfiguration.defaultWebpagePreferences ?? WKWebpagePreferences() + pagePrefs.preferredHTTPSNavigationPolicy = .automaticFallbackToHTTP + requiredConfiguration.defaultWebpagePreferences = pagePrefs + } + let webView = TabWebView(frame: .zero, configuration: requiredConfiguration, windowUUID: windowUUID) webView.configure(delegate: self, navigationDelegate: navigationDelegate) webView.accessibilityLabel = .WebViewAccessibilityLabel @@ -598,12 +606,17 @@ class Tab: NSObject, guard let currentlyOpenUrl = lastKnownUrl ?? historyList.last else { return } url = currentlyOpenUrl - close() + + // We're closing the tab in the UI, and remove the tabs from the tabmanager.tabs array right away + // so everything is kept in sync. But the actual closure of the Tab object is asynchronous [FXIOS-15339]. + Task { + await close() + } } - func close() { - webView?.pauseAllMediaPlayback {} - webView?.closeAllMediaPresentations {} + func close() async { + await webView?.pauseAllMediaPlayback() + await webView?.closeAllMediaPresentations() webView?.stopLoading() contentScriptManager.uninstall(tab: self) @@ -695,7 +708,7 @@ class Tab: NSObject, // FXIOS-14783: Experimentation on removing the isAboutHome check // isAboutHome: Do not reload from origin for homepage internal URLs should not be needed anymore let isAboutHome = InternalURL(url)?.isAboutHomeURL ?? false - let experimentEnabled = featureFlags.isFeatureEnabled(.needsReloadRefactor, checking: .buildOnly) + let experimentEnabled = featureFlagsProvider.isEnabled(.needsReloadRefactor) if experimentEnabled || !isAboutHome { webView.reloadFromOrigin() diff --git a/firefox-ios/Client/TabManagement/TabManager.swift b/firefox-ios/Client/TabManagement/TabManager.swift index 413e79ebd0a7d..8f33d0e44e365 100644 --- a/firefox-ios/Client/TabManagement/TabManager.swift +++ b/firefox-ios/Client/TabManagement/TabManager.swift @@ -23,7 +23,6 @@ protocol TabManager: AnyObject { var recentlyAccessedNormalTabs: [Tab] { get } var selectedTab: Tab? { get } - var backupCloseTab: BackupCloseTab? { get set } var tabs: [Tab] { get } var normalTabs: [Tab] { get } @@ -87,11 +86,6 @@ protocol TabManager: AnyObject { /// Remove normal tabs older than a certain period of time func removeNormalTabsOlderThan(period: TabsDeletionPeriod, currentDate: Date) - // MARK: - Undo Close - func undoCloseTab() - /// Undo close all tabs, it will restore the tabs that were backed up when the close action was called. - func undoCloseAllTabs() - // MARK: Get Tab func getTabForUUID(uuid: TabUUID) -> Tab? func getTabForURL(_ url: URL) -> Tab? diff --git a/firefox-ios/Client/TabManagement/TabManagerImplementation.swift b/firefox-ios/Client/TabManagement/TabManagerImplementation.swift index cc745dde5e2f1..052da4b27f71f 100644 --- a/firefox-ios/Client/TabManagement/TabManagerImplementation.swift +++ b/firefox-ios/Client/TabManagement/TabManagerImplementation.swift @@ -14,20 +14,11 @@ enum SwitchPrivacyModeResult { case usedExistingTab } -struct BackupCloseTab { - var tab: Tab - var restorePosition: Int? - var isSelected: Bool -} - -final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggable { +final class TabManagerImplementation: NSObject, TabManager, FeatureFlaggable { let windowUUID: WindowUUID var tabEventWindowResponseType: TabEventHandlerWindowResponseType { return .singleWindow(windowUUID) } var isRestoringTabs = false - // FXIOS-15007 - `backupCloseTab` is never niled when the undo toast is cleared, - // causing us to retain the tab object indefinitely - var backupCloseTab: BackupCloseTab? var notificationCenter: NotificationProtocol private(set) var tabs: [Tab] { didSet { @@ -39,7 +30,7 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab private var tabsInternalCache: (normal: [Tab], private: [Tab])? var isDeeplinkOptimizationRefactorEnabled: Bool { - return featureFlags.isFeatureEnabled(.deeplinkOptimizationRefactor, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.deeplinkOptimizationRefactor) } var count: Int { @@ -85,7 +76,6 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab private let windowIsNew: Bool private let profile: Profile private weak var navigationDelegate: WKNavigationDelegate? - private var backupCloseTabs = [Tab]() private var tabsTelemetry = TabsTelemetry() private var delegates = [WeakTabManagerDelegate]() // The only tab present before doing tab restoration, since deeplink happens before it @@ -206,13 +196,13 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab guard let index = tabs.firstIndex(where: { $0.tabUUID == tabUUID }) else { return } let tab = tabs[index] - self.removeTab(tab, flushToDisk: true) - self.updateSelectedTabAfterRemovalOf(tab, deletedIndex: index) + removeTab(tab, flushToDisk: true) + updateSelectedTabAfterRemovalOf(tab, deletedIndex: index) } func removeTabs(_ tabs: [Tab]) { for tab in tabs { - self.removeTab(tab, flushToDisk: false) + removeTab(tab, flushToDisk: false) } commitChanges() } @@ -230,18 +220,6 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab func removeAllTabs(isPrivateMode: Bool) { let currentModeTabs = tabs.filter { $0.isPrivate == isPrivateMode } - var currentSelectedTab: BackupCloseTab? - - // Backup the selected tab in separate variable as the `removeTab` method called below for each tab will - // automatically update tab selection as if there was a single tab removal. - if let tab = selectedTab, tab.isPrivate == isPrivateMode { - currentSelectedTab = BackupCloseTab(tab: tab, - restorePosition: tabs.firstIndex(of: tab), - isSelected: selectedTab?.tabUUID == tab.tabUUID) - } - - // Backup tabs for tab undo, this is not a feature on iPhone but is on iPad - backupCloseTabs = tabs // Scroll position for most tabs has been stored via session data when we navigate away, // but we need to save the selected tabs session data to persist scroll position @@ -249,7 +227,7 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab for tab in currentModeTabs { // Remove each tab without persisting changes - self.removeTab(tab, flushToDisk: false) + removeTab(tab, flushToDisk: false) if tab == currentModeTabs.last { // Select tab calls preserve tabs so we don't need to call it again if tab.isPrivate, @@ -262,8 +240,6 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab } } } - // Save the tab state that existed prior to removals (preserves original selected tab) - backupCloseTab = currentSelectedTab } /// Remove a tab, will notify delegate of the tab removal @@ -285,9 +261,6 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab } tab.cancelDocumentDownload() - backupCloseTab = BackupCloseTab(tab: tab, - restorePosition: removalIndex, - isSelected: selectedTab?.tabUUID == tab.tabUUID) let prevCount = count tabs.remove(at: removalIndex) assert(count == prevCount - 1, "Make sure the tab count was actually removed") @@ -297,7 +270,11 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab category: .tabs) } - tab.close() + // We're closing the tab in the UI, and remove the tabs from the tabmanager.tabs array right away + // so everything is kept in sync. But the actual closure of the Tab object is asynchronous [FXIOS-15339]. + Task { + await tab.close() + } // Notify of tab removal self.delegates.forEach { @@ -422,40 +399,6 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab return tabs.first(where: { $0.webView?.url == url }) } - // MARK: - Undo Close Tab - func undoCloseTab() { - assert(Thread.isMainThread) - guard let backupCloseTab = self.backupCloseTab else { return } - - let previouslySelectedTab = selectedTab - if let index = backupCloseTab.restorePosition { - tabs.insert(backupCloseTab.tab, at: index) - } else { - tabs.append(backupCloseTab.tab) - } - - if backupCloseTab.isSelected { - self.selectTab(backupCloseTab.tab) - } else if let tabToSelect = previouslySelectedTab { - self.selectTab(tabToSelect) - } - - delegates.forEach { $0.get()?.tabManagerUpdateCount() } - commitChanges() - } - - func undoCloseAllTabs() { - assert(Thread.isMainThread) - guard !backupCloseTabs.isEmpty else { return } - tabs = backupCloseTabs - commitChanges() - backupCloseTabs = [Tab]() - if backupCloseTab != nil { - selectTab(backupCloseTab?.tab) - backupCloseTab = nil - } - } - // MARK: - Restore tabs func restoreTabs() { @@ -929,7 +872,11 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab selectedIndex = -1 } privateTabs.forEach { tab in - tab.close() + // We're closing the tab in the UI, and remove the tabs from the tabmanager.tabs array right away + // so everything is kept in sync. But the actual closure of the Tab object is asynchronous [FXIOS-15339]. + Task { + await tab.close() + } delegates.forEach { $0.get()?.tabManager(self, didRemoveTab: tab, isRestoring: false) } } @@ -942,7 +889,7 @@ final class TabManagerImplementation: NSObject, TabManager, LegacyFeatureFlaggab private func didSelectTab(_ url: URL?) { tabsTelemetry.stopTabSwitchMeasurement() - let isNativeErrorPage = featureFlags.isFeatureEnabled(.nativeErrorPage, checking: .buildOnly) + let isNativeErrorPage = featureFlagsProvider.isEnabled(.nativeErrorPage) // If app starts with error url, first homepage appears and // then error page is loaded. To directly load error page diff --git a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift index ff41bb69e3417..f25aa1a353e1e 100644 --- a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift +++ b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift @@ -41,7 +41,7 @@ enum SearchLocation: String { // FIXME: FXIOS-13987 Make truly thread safe class TelemetryWrapper: TelemetryWrapperProtocol, - LegacyFeatureFlaggable, + UserFeaturePreferenceProvider, Notifiable, @unchecked Sendable { typealias ExtraKey = TelemetryWrapper.EventExtraKey @@ -323,7 +323,7 @@ class TelemetryWrapper: TelemetryWrapperProtocol, GleanMetrics.WallpaperAnalytics.themedWallpaper[currentWallpaper.id.lowercased()].add() } - let startAtHomeOption: StartAtHome = featureFlags.getCustomState(for: .startAtHome) ?? .afterFourHours + let startAtHomeOption = userPreferences.startAtHomeSetting GleanMetrics.Preferences.openingScreen.set(startAtHomeOption.rawValue) // Record summarizer user preferences diff --git a/firefox-ios/Client/Telemetry/ToastTelemetry.swift b/firefox-ios/Client/Telemetry/ToastTelemetry.swift deleted file mode 100644 index 2b2e7a5f649a0..0000000000000 --- a/firefox-ios/Client/Telemetry/ToastTelemetry.swift +++ /dev/null @@ -1,22 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// 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 Foundation -import Glean - -struct ToastTelemetry { - private let gleanWrapper: GleanWrapper - - init(gleanWrapper: GleanWrapper = DefaultGleanWrapper()) { - self.gleanWrapper = gleanWrapper - } - - func undoClosedSingleTab() { - gleanWrapper.recordEvent(for: GleanMetrics.ToastsCloseSingleTab.undoTapped) - } - - func undoClosedAllTabs() { - gleanWrapper.recordEvent(for: GleanMetrics.ToastsCloseAllTabs.undoTapped) - } -} diff --git a/firefox-ios/Client/TermsOfServiceManager.swift b/firefox-ios/Client/TermsOfServiceManager.swift index b9bc04ab36609..6e989f0045b11 100644 --- a/firefox-ios/Client/TermsOfServiceManager.swift +++ b/firefox-ios/Client/TermsOfServiceManager.swift @@ -8,7 +8,7 @@ import Glean import MozillaAppServices import OnboardingKit -struct TermsOfServiceManager: LegacyFeatureFlaggable, Sendable { +struct TermsOfServiceManager: FeatureFlaggable, Sendable { var prefs: Prefs init(prefs: Prefs) { @@ -16,11 +16,11 @@ struct TermsOfServiceManager: LegacyFeatureFlaggable, Sendable { } var isModernOnboardingEnabled: Bool { - featureFlags.isFeatureEnabled(.modernOnboardingUI, checking: .buildAndUser) + featureFlagsProvider.isEnabled(.modernOnboardingUI) } var isFeatureEnabled: Bool { - featureFlags.isFeatureEnabled(.tosFeature, checking: .buildAndUser) + featureFlagsProvider.isEnabled(.tosFeature) } var isAccepted: Bool { @@ -28,7 +28,7 @@ struct TermsOfServiceManager: LegacyFeatureFlaggable, Sendable { } var shouldShowScreen: Bool { - guard featureFlags.isFeatureEnabled(.tosFeature, checking: .buildAndUser) else { return false } + guard featureFlagsProvider.isEnabled(.tosFeature) else { return false } return prefs.boolForKey(PrefsKeys.TermsOfUseAccepted) == nil } diff --git a/firefox-ios/Client/uk.lproj/InfoPlist.strings b/firefox-ios/Client/uk.lproj/InfoPlist.strings index 274d50ec67bc5..b669b2f9f80c6 100644 --- a/firefox-ios/Client/uk.lproj/InfoPlist.strings +++ b/firefox-ios/Client/uk.lproj/InfoPlist.strings @@ -19,6 +19,9 @@ /* Privacy - Photo Library Additions Usage Description */ "NSPhotoLibraryAddUsageDescription" = "Це дозволяє вам зберігати фотографії."; +/* Privacy - Speech Recognition Usage Description */ +"NSSpeechRecognitionUsageDescription" = "Firefox використовує розпізнавання мовлення, щоб перетворювати ваш голос на текст для голосового пошуку."; + /* (No Comment) */ "Scan QR Code" = "Сканувати QR-код"; diff --git a/firefox-ios/Extensions/ActionExtension/tt.lproj/InfoPlist.strings b/firefox-ios/Extensions/ActionExtension/tt.lproj/InfoPlist.strings new file mode 100644 index 0000000000000..9134ff7130e26 --- /dev/null +++ b/firefox-ios/Extensions/ActionExtension/tt.lproj/InfoPlist.strings @@ -0,0 +1,3 @@ +/* Bundle display name */ +"CFBundleDisplayName" = "Firefox-та ачу"; + diff --git a/firefox-ios/Providers/Merino/MerinoFeedFetcher.swift b/firefox-ios/Providers/Merino/MerinoFeedFetcher.swift index eb96dfc228bac..00c3b5bba897f 100644 --- a/firefox-ios/Providers/Merino/MerinoFeedFetcher.swift +++ b/firefox-ios/Providers/Merino/MerinoFeedFetcher.swift @@ -14,7 +14,7 @@ protocol MerinoFeedFetching: Sendable { ) async -> CuratedRecommendationsResponse? } -struct MerinoFeedFetcher: MerinoFeedFetching, LegacyFeatureFlaggable { +struct MerinoFeedFetcher: MerinoFeedFetching, FeatureFlaggable { let baseURL: String let logger: Logger @@ -31,7 +31,7 @@ struct MerinoFeedFetcher: MerinoFeedFetching, LegacyFeatureFlaggable { userAgentHeader: userAgent ) ) - if featureFlags.isFeatureEnabled(.homepageStoryCategories, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.homepageStoryCategories) { return try client.getCuratedRecommendations( request: CuratedRecommendationsRequest( diff --git a/firefox-ios/Providers/Merino/MerinoProvider.swift b/firefox-ios/Providers/Merino/MerinoProvider.swift index 0855417829539..5ab411b9a0d2d 100644 --- a/firefox-ios/Providers/Merino/MerinoProvider.swift +++ b/firefox-ios/Providers/Merino/MerinoProvider.swift @@ -11,7 +11,7 @@ protocol MerinoStoriesProviding: Sendable { func fetchContent() async throws -> CuratedRecommendationsResponse } -final class MerinoProvider: MerinoStoriesProviding, LegacyFeatureFlaggable, @unchecked Sendable { +final class MerinoProvider: MerinoStoriesProviding, FeatureFlaggable, @unchecked Sendable { private struct Constants { static let merinoServicesBaseURL = "https://merino.services.mozilla.com" static let numberOfStoriesToFetchForCaching = 100 @@ -117,7 +117,7 @@ final class MerinoProvider: MerinoStoriesProviding, LegacyFeatureFlaggable, @unc } private var isHomepageStoryCategoriesEnabled: Bool { - return featureFlags.isFeatureEnabled(.homepageStoryCategories, checking: .buildOnly) + return featureFlagsProvider.isEnabled(.homepageStoryCategories) } private func iOSToMerinoLocale(from locale: String) -> CuratedRecommendationLocale? { diff --git a/firefox-ios/Providers/Profile.swift b/firefox-ios/Providers/Profile.swift index a654afef90bac..d828e27f773fd 100644 --- a/firefox-ios/Providers/Profile.swift +++ b/firefox-ios/Providers/Profile.swift @@ -28,7 +28,7 @@ import struct MozillaAppServices.SyncParams import struct MozillaAppServices.SyncResult import struct MozillaAppServices.VisitObservation import struct MozillaAppServices.PendingCommand -import struct MozillaAppServices.RemoteSettingsConfig2 +import struct MozillaAppServices.RemoteSettingsConfig // TODO: FXIOS-14225 - SyncManager shouldn't be Sendable public protocol SyncManager: Sendable { @@ -141,7 +141,6 @@ protocol Profile: AnyObject, Sendable { func storeAndSyncTabs(_ tabs: [RemoteTab]) -> Deferred> func addTabToCommandQueue(_ deviceId: String, url: URL) - func removeTabFromCommandQueue(_ deviceId: String, url: URL) func flushTabCommands(toDeviceId: String?) func sendItem(_ item: ShareItem, toDevices devices: [RemoteDevice]) -> Success @@ -544,10 +543,6 @@ open class BrowserProfile: Profile, tabs.addRemoteCommand(deviceId: deviceId, url: url) } - func removeTabFromCommandQueue(_ deviceId: String, url: URL) { - tabs.removeRemoteCommand(deviceId: deviceId, url: url) - } - public func sendItem(_ item: ShareItem, toDevices devices: [RemoteDevice]) -> Success { let deferred = Success() if let accountManager = RustFirefoxAccounts.shared.accountManager { @@ -659,9 +654,11 @@ open class BrowserProfile: Profile, let remoteSettingsEnvironment = RemoteSettingsEnvironment(rawValue: remoteSettingsEnvironmentKey) ?? .prod let remoteSettingsServer = remoteSettingsEnvironment.toRemoteSettingsServer() let bucketName = (remoteSettingsServer == .prod ? "main" : "main-preview") - let config = RemoteSettingsConfig2(server: remoteSettingsServer, - bucketName: bucketName, - appContext: remoteSettingsAppContext()) + let config = RemoteSettingsConfig( + server: remoteSettingsServer, + bucketName: bucketName, + appContext: remoteSettingsAppContext() + ) let url = URL(fileURLWithPath: directory, isDirectory: true).appendingPathComponent("remote-settings") let path = url.path diff --git a/firefox-ios/Providers/TopSitesProvider.swift b/firefox-ios/Providers/TopSitesProvider.swift index f4742c66e59f3..f3f8954d45724 100644 --- a/firefox-ios/Providers/TopSitesProvider.swift +++ b/firefox-ios/Providers/TopSitesProvider.swift @@ -36,7 +36,7 @@ extension TopSitesProvider { } } -final class TopSitesProviderImplementation: TopSitesProvider, LegacyFeatureFlaggable { +final class TopSitesProviderImplementation: TopSitesProvider, FeatureFlaggable { private let pinnedSiteFetcher: PinnedSites private let placesFetcher: RustPlaces private let prefs: Prefs @@ -52,8 +52,7 @@ final class TopSitesProviderImplementation: TopSitesProvider, LegacyFeatureFlagg } private var shouldExcludeFirefoxJpGuide: Bool { - let isFirefoxJpGuideDefaultSiteEnabled = featureFlags.isFeatureEnabled(.firefoxJpGuideDefaultSite, - checking: .buildOnly) + let isFirefoxJpGuideDefaultSiteEnabled = featureFlagsProvider.isEnabled(.firefoxJpGuideDefaultSite) let locale = Locale.current return locale.identifier == "ja_JP" && !isFirefoxJpGuideDefaultSiteEnabled } diff --git a/firefox-ios/Shared/Strings.swift b/firefox-ios/Shared/Strings.swift index 232969c1de32d..da8f702bdd6d6 100644 --- a/firefox-ios/Shared/Strings.swift +++ b/firefox-ios/Shared/Strings.swift @@ -949,6 +949,356 @@ extension String { } } +extension String { + public struct WorldCup { + public struct CountryPicker { + public static let Title = MZLocalizedString( + key: "WorldCup.CountryPicker.Title.v151", + tableName: "WorldCup", + value: "Follow Your Team", + comment: "Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event.") + public static let SkipButtonTitle = MZLocalizedString( + key: "WorldCup.CountryPicker.SkipButton.Title.v151", + tableName: "WorldCup", + value: "Skip", + comment: "Label for the skip button on the country picker for the World Cup widget. This shows to the user that you could skip the selection of a country to follow.") + public static let CloseButtonAccessibilityLabel = MZLocalizedString( + key: "WorldCup.CountryPicker.Close.AccessibilityLabel.v151", + tableName: "WorldCup", + value: "Close World Cup country picker", + comment: "Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker.") + // Note: "North America" and "CONCACAF" overlap in scope + // CONCACAF is the FIFA confederation covering North America, Central America, and the Caribbean. + // For completeness, we are including a all the geographic region headers as well as the CONCACAF confederation header + // in case the UI groups countries by geographic region rather than strictly by confederation. + public struct Confederation { + public static let NorthAmerica = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.NorthAmerica.v151", + tableName: "WorldCup", + value: "North America", + comment: "Section header for the North America region in the World Cup country picker.") + public static let CentralAmerica = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.CentralAmerica.v151", + tableName: "WorldCup", + value: "Central America", + comment: "Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation.") + public static let Africa = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.Africa.v151", + tableName: "WorldCup", + value: "Africa", + comment: "Section header for the Africa region in the World Cup country picker.") + public static let Asia = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.Asia.v151", + tableName: "WorldCup", + value: "Asia", + comment: "Section header for the Asia region in the World Cup country picker.") + public static let CONCACAF = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.CONCACAF.v151", + tableName: "WorldCup", + value: "CONCACAF", + comment: "Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean.") + public static let Europe = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.Europe.v151", + tableName: "WorldCup", + value: "Europe", + comment: "Section header for the Europe region in the World Cup country picker.") + public static let Oceania = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.Oceania.v151", + tableName: "WorldCup", + value: "Oceania", + comment: "Section header for the Oceania region in the World Cup country picker.") + public static let SouthAmerica = MZLocalizedString( + key: "WorldCup.CountryPicker.Confederation.SouthAmerica.v151", + tableName: "WorldCup", + value: "South America", + comment: "Section header for the South America region in the World Cup country picker.") + } + + public struct CountryName { + public static let England = MZLocalizedString( + key: "WorldCup.CountryPicker.Country.England.v151", + tableName: "WorldCup", + value: "England", + comment: "Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string.") + public static let Scotland = MZLocalizedString( + key: "WorldCup.CountryPicker.Country.Scotland.v151", + tableName: "WorldCup", + value: "Scotland", + comment: "Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string.") + } + } + + public struct HomepageWidget { + public static let LiveLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.LiveLabel.v151", + tableName: "WorldCup", + value: "LIVE", + comment: "The label that appears for live World Cup matches.") + public static let DisableNotificationButtonAccessibilityLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151", + tableName: "WorldCup", + value: "Disable notifications", + comment: "The accessibility labels for the button that allow to disable World Cup notifications.") + public static let SettingsButtonAccessibilityLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151", + tableName: "WorldCup", + value: "More options", + comment: "The accessibility label for the button that allows to see more options related to World Cup widget.") + public static let ChangeTeamLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.ChangeTeamLabel.v151", + tableName: "WorldCup", + value: "Change team", + comment: "The label for the button that allows to change the team displayed in the World Cup widget more options panel.") + public static let FollowTeamLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.FollowTeamLabel.v151", + tableName: "WorldCup", + value: "Follow team", + comment: "The label for the button that allows to follow a team displayed in the World Cup widget more options panel when no previous team was selected.") + public static let GetCustomWallpaperLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151", + tableName: "WorldCup", + value: "Get custom wallpaper", + comment: "The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel.") + public static let ShareLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.ShareLabel.v151", + tableName: "WorldCup", + value: "Share", + comment: "The label for the button that allows to share the selected match info in the World Cup widget more options panel.") + public static let RemoveLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RemoveLabel.v151", + tableName: "WorldCup", + value: "Remove", + comment: "The label for the button that allows to remove the World Cup widget from the home screen in the more options panel.") + public static let FullTimeLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.FTLabel.v151", + tableName: "WorldCup", + value: "(Full Time)", + comment: "The label indicating that the displaying match as ended.") + public static let FullTimePenaltiesLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151", + tableName: "WorldCup", + value: "Full time • Penalties", + comment: "The label indicating the displaying match has ended after penalties.") + public static let ErrorLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.ErrorLabel.v151", + tableName: "WorldCup", + value: "We couldn’t load match data. Please refresh.", + comment: "A generic error message used in the World Cup widget when the match data could not be loaded.") + public static let RetryButtonLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RetryButtonLabel.v151", + tableName: "WorldCup", + value: "Refresh", + comment: "The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed.") + public struct CountDown { + public static let Title = MZLocalizedString( + key: "WorldCup.HomepageWidget.CountDown.Title.v151", + tableName: "WorldCup", + value: "Countdown to the World Cup", + comment: "Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event.") + public static let DayLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.CountDown.DayLabel.v151", + tableName: "WorldCup", + value: "D", + comment: "D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues.") + public static let HourLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.CountDown.HourLabel.v151", + tableName: "WorldCup", + value: "H", + comment: "H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues.") + public static let MinuteLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.CountDown.MinuteLabel.v151", + tableName: "WorldCup", + value: "M", + comment: "M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues.") + public static let ViewScheduleButtonLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.CountDown.ViewScheduleButtonLabel.v151", + tableName: "WorldCup", + value: "View Schedule", + comment: "Label for the button that takes users to the World Cup schedule website on the countdown section.") + } + public struct FollowTeamCard { + public static let Title = MZLocalizedString( + key: "WorldCup.HomepageWidget.FollowTeamCard.Title.v151", + tableName: "WorldCup", + value: "Keep Tabs on the World Cup", + comment: "Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed.") + public static let Description = MZLocalizedString( + key: "WorldCup.HomepageWidget.FollowTeamCard.Description.v151", + tableName: "WorldCup", + value: "Get live match updates and more.", + comment: "Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event.") + public static let CTA = MZLocalizedString( + key: "WorldCup.HomepageWidget.FollowTeamCard.CTA.v151", + tableName: "WorldCup", + value: "Follow Your Team", + comment: "Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event.") + public static let CloseButtonAccessibilityLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151", + tableName: "WorldCup", + value: "Hide World Cup updates", + comment: "Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event.") + } + + public struct TemporaryView { + public static let Description = MZLocalizedString( + key: "WorldCup.HomepageWidget.TemporaryView.Description.v151", + tableName: "WorldCup", + value: "We’ll keep you updated as the World Cup approaches", + comment: "The description for the temporary view in the World Cup widget showing the team that has been previously selected.") + } + + public struct GroupPhase { + public static let GroupA = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupA.Title.v151", + tableName: "WorldCup", + value: "Group A", + comment: "The title of the Group A group in the World Cup Group Stage.") + public static let GroupB = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupB.Title.v151", + tableName: "WorldCup", + value: "Group B", + comment: "The title of the Group B group in the World Cup Group Stage.") + public static let GroupC = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupC.Title.v151", + tableName: "WorldCup", + value: "Group C", + comment: "The title of the Group C group in the World Cup Group Stage.") + public static let GroupD = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupD.Title.v151", + tableName: "WorldCup", + value: "Group D", + comment: "The title of the Group D group in the World Cup Group Stage.") + public static let GroupE = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupE.Title.v151", + tableName: "WorldCup", + value: "Group E", + comment: "The title of the Group E group in the World Cup Group Stage.") + public static let GroupF = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupF.Title.v151", + tableName: "WorldCup", + value: "Group F", + comment: "The title of the Group F group in the World Cup Group Stage.") + public static let GroupG = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupG.Title.v151", + tableName: "WorldCup", + value: "Group G", + comment: "The title of the Group G group in the World Cup Group Stage.") + public static let GroupH = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupH.Title.v151", + tableName: "WorldCup", + value: "Group H", + comment: "The title of the Group H group in the World Cup Group Stage.") + public static let GroupI = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupI.Title.v151", + tableName: "WorldCup", + value: "Group I", + comment: "The title of the Group I group in the World Cup Group Stage.") + public static let GroupJ = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupJ.Title.v151", + tableName: "WorldCup", + value: "Group J", + comment: "The title of the Group J group in the World Cup Group Stage.") + public static let GroupK = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupK.Title.v151", + tableName: "WorldCup", + value: "Group K", + comment: "The title of the Group K group in the World Cup Group Stage.") + public static let GroupL = MZLocalizedString( + key: "WorldCup.GroupPhase.GroupL.Title.v151", + tableName: "WorldCup", + value: "Group L", + comment: "The title of the Group L group in the World Cup Group Stage.") + public static let RelatedMatchesLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151", + tableName: "WorldCup", + value: "Related matches", + comment: "The label for the section in the World Cup widget showing the related matches of a team for the group phase.") + } + + public struct RoundPhase { + public static let ScrollIndicatorAccessibilityLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151", + tableName: "WorldCup", + value: "Scroll to see previous or next matches", + comment: "The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget.") + public static let Round32Label = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.Round32Label.v151", + tableName: "WorldCup", + value: "ROUND OF 32", + comment: "The label for the 'Round of 32' phase in the World Cup widget.") + public static let Round16Label = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.Round16Label.v151", + tableName: "WorldCup", + value: "ROUND OF 16", + comment: "The label for the 'Round of 16' phase in the World Cup widget.") + public static let QuarterFinalsLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151", + tableName: "WorldCup", + value: "QUARTER-FINALS", + comment: "The label for the 'Quarter-finals' phase in the World Cup widget.") + public static let SemiFinalsLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151", + tableName: "WorldCup", + value: "SEMI-FINALS", + comment: "The label for the 'Semi-finals' phase in the World Cup widget.") + public static let BronzeFinalLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151", + tableName: "WorldCup", + value: "BRONZE FINAL", + comment: "The label for the 'Bronze final' match in the World Cup widget.") + public static let FinalLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151", + tableName: "WorldCup", + value: "FINAL", + comment: "The label for the 'Final' match in the World Cup widget.") + public static let UpcomingLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151", + tableName: "WorldCup", + value: "Upcoming", + comment: "The label for an upcoming match in the World Cup widget round phase.") + public static let ThirdPlaceLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.ThirdPlaceLabel.v151", + tableName: "WorldCup", + value: "THIRD PLACE", + comment: "The label for the 'Third place' winner in the World Cup widget.") + public static let WinWorldCupLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151", + tableName: "WorldCup", + value: "2026 WORLD CUP CHAMPIONS", + comment: "The label for the World Cup championship winner in the World Cup widget.") + } + + public struct EliminatedTeamSection { + public static let Title = MZLocalizedString( + key: "WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151", + tableName: "WorldCup", + value: "Still want to Follow Along?", + comment: "The title of the section in the World Cup widget that shows when the selected team was eliminated.") + public static let Description = MZLocalizedString( + key: "WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151", + tableName: "WorldCup", + value: "Choose another team to keep up with the World Cup.", + comment: "The description of the section in the World Cup widget that shows when the selected team was eliminated.") + public static let RemoveButtonLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151", + tableName: "WorldCup", + value: "Remove", + comment: "The label for the button in the eliminated section that removes the World Cup widget from the homepage.") + public static let RemoveButtonAccessibilityLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151", + tableName: "WorldCup", + value: "Remove the World Cup widget", + comment: "The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage.") + public static let ChooseTeamButtonLabel = MZLocalizedString( + key: "WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151", + tableName: "WorldCup", + value: "Choose Team", + comment: "The label for the button in the eliminated section that takes the user back to the team selection screen.") + } + } + } +} + // MARK: - Firefox Homepage extension String { public struct FirefoxHomepage { @@ -1227,7 +1577,7 @@ extension String { key: "Keyboard.Shortcuts.ShowDownloads", tableName: nil, value: "Show Downloads", - comment: "A label indcating the keyboard shortcut of showing all downloads. This label is displayed in the Discoverability overlay when a user presses the Command key. The Discoverability overlay and shortcut become available only when a user has connected a hardware keyboard to an iPad. See https://drive.google.com/file/d/1gH3tbvDceg7yG5N67NIHS-AXgDgCzBHN/view?usp=sharing for more details.") + comment: "A label indicating the keyboard shortcut of showing all downloads. This label is displayed in the Discoverability overlay when a user presses the Command key. The Discoverability overlay and shortcut become available only when a user has connected a hardware keyboard to an iPad. See https://drive.google.com/file/d/1gH3tbvDceg7yG5N67NIHS-AXgDgCzBHN/view?usp=sharing for more details.") public static let ShowFirstTab = MZLocalizedString( key: "Keyboard.Shortcuts.ShowFirstTab", tableName: nil, @@ -1289,7 +1639,7 @@ extension String { key: "Keyboard.Shortcuts.Section.Window", tableName: nil, value: "Window", - comment: "A label indicating a grouping of related keyboard shortcuts describing actions a user can take when navigating between their availale set of tabs. This label is displayed inside the Discoverability overlay when a user presses the Command key. The Discoverability overlay and shortcut become available only when a user has connected a hardware keyboard to an iPad. See https://drive.google.com/file/d/1gH3tbvDceg7yG5N67NIHS-AXgDgCzBHN/view?usp=sharing for more details.") + comment: "A label indicating a grouping of related keyboard shortcuts describing actions a user can take when navigating between their available set of tabs. This label is displayed inside the Discoverability overlay when a user presses the Command key. The Discoverability overlay and shortcut become available only when a user has connected a hardware keyboard to an iPad. See https://drive.google.com/file/d/1gH3tbvDceg7yG5N67NIHS-AXgDgCzBHN/view?usp=sharing for more details.") } } } @@ -2809,6 +3159,11 @@ extension String { tableName: "CustomizeFirefoxHome", value: "Top Stories", comment: "In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off") + public static let WorldCup = MZLocalizedString( + key: "Settings.Home.Option.WorldCup.v151", + tableName: "CustomizeFirefoxHome", + value: "World Cup", + comment: "In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off") public static let Title = MZLocalizedString( key: "Settings.Home.Option.Title.v101", tableName: nil, @@ -2856,7 +3211,7 @@ extension String { key: "Settings.Homepage.Shortcuts.SponsoredShortcutsToggle.v100", tableName: nil, value: "Sponsored Shortcuts", - comment: "This string is the title of the toggle to disable the sponsored shortcuts functionnality which can be enabled in the shortcut sections. This toggle is in the settings page.") + comment: "This string is the title of the toggle to disable the sponsored shortcuts functionality which can be enabled in the shortcut sections. This toggle is in the settings page.") public static let Rows = MZLocalizedString( key: "Settings.Homepage.Shortcuts.Rows.v100", tableName: nil, @@ -3090,9 +3445,9 @@ extension String { ) public static let BlockedStatusDescription = MZLocalizedString( - key: "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151", + key: "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151", tableName: "Settings", - value: "**Blocked**: you won’t see and can’t use the feature. For on-device AI, any downloaded models are removed.", + value: "**Blocked**: You won’t see and can’t use the feature. For on-device AI, any downloaded models are removed.", comment: "In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation." ) } @@ -3990,11 +4345,6 @@ extension String { comment: "When user choose to have Blank Page as homepage, this will be displayed as tab title.") public struct CloseTabsToast { - public static let Title = MZLocalizedString( - key: "CloseTabsToast.Title.v113", - tableName: "TabsTray", - value: "Tabs Closed: %d", - comment: "When the user closes tabs in the tab tray, a popup will appear informing them how many tabs were closed. This is the text for the popup. %d is the number of tabs. ") public static let SingleTabTitle = MZLocalizedString( key: "CloseTabsToast.SingleTabTitle.v113", tableName: "TabsTray", @@ -4894,7 +5244,7 @@ extension String { key: "Search.ThirdPartyEngines.AddSuccess", tableName: nil, value: "Added Search engine!", - comment: "The success message that appears after a user sucessfully adds a new search engine") + comment: "The success message that appears after a user successfully adds a new search engine") public static let ThirdPartySearchAddTitle = MZLocalizedString( key: "Search.ThirdPartyEngines.AddTitle", tableName: nil, @@ -6217,13 +6567,13 @@ extension String { key: "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150", tableName: "EnhancedTrackingProtection", value: "Standard blocks common trackers after a page starts loading, so you may see a higher tracker count. %@", - comment: "Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more") + comment: "Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more") public static let trackersBlockedStrictModeFooterText = MZLocalizedString( key: "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150", tableName: "EnhancedTrackingProtection", value: "Strict blocks more trackers by stopping them before a page loads, so you may see a lower tracker count. %@", - comment: "Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more") + comment: "Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more") public static let trackersBlockedFooterTextLink = MZLocalizedString( key: "Menu.EnhancedTrackingProtection.Link.LearnMore.v150", @@ -7018,17 +7368,17 @@ extension String { key: "TodayWidget.TopSitesGalleryTitle", tableName: "Today", value: "Top Sites", - comment: "Title for top sites widget to add Firefox top sites shotcuts to home screen") + comment: "Title for top sites widget to add Firefox top sites shortcuts to home screen") public static let TopSitesGalleryTitleV2 = MZLocalizedString( key: "TodayWidget.TopSitesGalleryTitleV2", tableName: "Today", value: "Website Shortcuts", - comment: "Title for top sites widget to add Firefox top sites shotcuts to home screen") + comment: "Title for top sites widget to add Firefox top sites shortcuts to home screen") public static let TopSitesGalleryDescription = MZLocalizedString( key: "TodayWidget.TopSitesGalleryDescription", tableName: "Today", value: "Add shortcuts to frequently and recently visited sites.", - comment: "Description for top sites widget to add Firefox top sites shotcuts to home screen") + comment: "Description for top sites widget to add Firefox top sites shortcuts to home screen") // Quick View Open Tabs - Medium Size Widget public static let MoreTabsLabel = MZLocalizedString( @@ -7477,7 +7827,7 @@ extension String { key: "Toolbar.Tabs.Button.A11y.Label.v135", tableName: "Toolbar", value: "Tabs open", - comment: "Accessibility label for the tabs button in the toolbar, specifing the number of tabs open.") + comment: "Accessibility label for the tabs button in the toolbar, specifying the number of tabs open.") public static let TabsButtonLargeContentTitle = MZLocalizedString( key: "Toolbar.Tabs.Button.A11y.LargeContentTitle.v137", @@ -7495,7 +7845,7 @@ extension String { key: "Toolbar.Menu.Button.A11y.Label.v135", tableName: "Toolbar", value: "Main Menu", - comment: "Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu") + comment: "Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu") public struct TabToolbarLongPressActionsMenu { public static let CloseThisTabButton = MZLocalizedString( @@ -8572,7 +8922,7 @@ extension String { key: "Menu.DownloadPDF.Confirm.v129", tableName: "Menu", value: "Successfully Downloaded PDF", - comment: "Toast displayed to user after downlaod pdf was pressed." + comment: "Toast displayed to user after download pdf was pressed." ) public static let Help = MZLocalizedString( key: "Menu.Help.v99", @@ -8660,6 +9010,24 @@ extension String { value: "Private Browsing Data Erased", comment: "When the user ends their private session, they are returned to the private mode homepage, and a toastbar popups confirming that their data has been erased. This is the label for that toast." ) + public static let TabTrayCloseTabsToastTitle = MZLocalizedString( + key: "CloseTabsToast.Title.v113", + tableName: "TabsTray", + value: "Tabs Closed: %d", + comment: "When the user closes tabs in the tab tray, a popup will appear informing them how many tabs were closed. This is the text for the popup. %d is the number of tabs. ") + } + struct v151 { + public static let BlockedStatusDescription = MZLocalizedString( + key: "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151", + tableName: "Settings", + value: "**Blocked**: you won’t see and can’t use the feature. For on-device AI, any downloaded models are removed.", + comment: "In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation." + ) + public static let DoneButtonTitle = MZLocalizedString( + key: "WorldCup.CountryPicker.DoneButtonTitle.v151", + tableName: "WorldCup", + value: "Done", + comment: "Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event.") } } } diff --git a/firefox-ios/Shared/Supporting Files/bg.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/bg.lproj/CustomizeFirefoxHome.strings index 0a30109d2be6a..1fe2d1d691b81 100644 --- a/firefox-ios/Shared/Supporting Files/bg.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/bg.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Популярни"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Световно първенство по футбол"; + diff --git a/firefox-ios/Shared/Supporting Files/bg.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/bg.lproj/EnhancedTrackingProtection.strings index b56467233114e..cae233664e331 100644 --- a/firefox-ios/Shared/Supporting Files/bg.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/bg.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Спирани проследявания: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Стандартно блокира често използвани тракери, след като страницата започне да се зарежда, така че е възможно да забележите по-голям брой тракери. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Строгата версия блокира повече тракери, като ги спира преди зареждане на страница, така че може да видите по-нисък брой тракери. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/bg.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/bg.lproj/Settings.strings index bad36d8400553..98079698d6504 100644 --- a/firefox-ios/Shared/Supporting Files/bg.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/bg.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Блокиране**: няма да виждате и няма да можете да използвате функцията. За AI върху устройство всички изтеглени модели се премахват."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Блокиране**: Няма да виждате и не можете да използвате функцията. За AI върху устройство всички изтеглени модели се премахват."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Страниците и обобщенията никога не се запазват."; diff --git a/firefox-ios/Shared/Supporting Files/bg.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/bg.lproj/Toolbar.strings index f4f4ea0e37639..5bf897842e2c6 100644 --- a/firefox-ios/Shared/Supporting Files/bg.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/bg.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Главно меню"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Затваряне на раздел"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Отворени раздели"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/bg.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/bg.lproj/WorldCup.strings new file mode 100644 index 0000000000000..4b7077b82ede0 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/bg.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Затваряне на инструмента за избор на държави на Световното първенство"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Африка"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Азия"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Централна Америка"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "КОНКАКАФ"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Европа"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Северна Америка"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Океания"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Южна Америка"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Англия"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Шотландия"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Готово"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Следвайте екипа си"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Група А"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Група В"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Група C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Група D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Група Е"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Група F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Група G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Група H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Група I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Група J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Група K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Група L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Смяна на отбора"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "И"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "H"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "М"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Обратно връщане до Световното първенство по футбол"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Изключване на известията"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Изберете Отбор"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Изберете друг отбор, за да сте в крак със Световното първенство."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Премахване на приспособлението за Световното първенство по футбол"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Премахване"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Все още ли искате да следвате?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Не можахме да заредим данни за съвпадение. Моля, презаредете."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Скриване на актуализациите за Световното първенство по футбол"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Следвайте екипа си"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Получавайте актуализации на мачовете на живо и още."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Следете Световното първенство по футбол"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Пълно работно време)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Вземете персонализиран тапет"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Свързани съвпадения"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "НА ЖИВО"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Премахване"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Опресняване"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "БРОНЗОВ ФИНАЛ"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "ФИНАЛ"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Край на времето • Дузпи"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "ЧЕТВЕРТ-ФИНАЛИ"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "ОСНОВА ФАЗА"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "ОСНОВА ФАЗА"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Превъртете, за да видите предишни или следващи съвпадения"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "ПОЛУФИНАЛИ"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "Предстоящи"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "ШАМПИОНИ ОТ СВЕТОВИТЕ СЪОБЩЕНИЯ 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Повече настройки"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Споделяне"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Ще ви държим в течение с наближаването на Световното първенство по футбол"; + diff --git a/firefox-ios/Shared/Supporting Files/br.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/br.lproj/Toolbar.strings index 4abd6e87b46e8..081a4c6a632cc 100644 --- a/firefox-ios/Shared/Supporting Files/br.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/br.lproj/Toolbar.strings @@ -4,7 +4,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Serriñ an Ivinell-mañ"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Ivinelloù digor"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/bs.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/bs.lproj/Toolbar.strings index 84efb6aca3e1c..ae58a43463ebd 100644 --- a/firefox-ios/Shared/Supporting Files/bs.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/bs.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Glavni meni"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zatvori ovaj tab"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Otvoreni tabovi"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ca.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ca.lproj/Toolbar.strings index b502ea081b35f..7011b5a0b7a44 100644 --- a/firefox-ios/Shared/Supporting Files/ca.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ca.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Tanca aquesta pestanya"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Pestanyes obertes"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/co.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/co.lproj/EnhancedTrackingProtection.strings index e7a8daec8a479..554d5919c08f4 100644 --- a/firefox-ios/Shared/Supporting Files/co.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/co.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Perseguitatori bluccati : %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "A prutezzione classica blucca i perseguitatori cumuni dopu à u caricamentu di e pagine, dunque si pò vede un numeru superiore di perseguitatori. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "A prutezzione severa blucca più perseguitatori piantenduli prima u caricamentu di a pagina, dunque si pò vede un numeru inferiore di perseguitatori. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/co.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/co.lproj/Toolbar.strings index 75615dae35a26..5a3bbb2c0193f 100644 --- a/firefox-ios/Shared/Supporting Files/co.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/co.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Listinu principale"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Chjode st’unghjetta"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Unghjette aperte"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/cs.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/cs.lproj/EnhancedTrackingProtection.strings index b989fb2f3fad9..87572973ecf3f 100644 --- a/firefox-ios/Shared/Supporting Files/cs.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/cs.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Zablokované sledovací prvky: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standardní úroveň blokuje běžné sledovací prvky poté, co se stránka začne načítat. Může proto zaznamenat vyšší počet sledovacích prvků. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Přísná úroveň blokuje více sledovacích prvků před načtením stránky. Může tedy zaznamenat nižší počet sledovacích prvků. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ @@ -107,7 +107,7 @@ "Menu.EnhancedTrackingProtection.On.Header.v128" = "Jste chráněni. Pokud něco zpozorujeme, dáme vám vědět."; /* Header for the enhanced tracking protection screen when the user has selected to be protected but the connection is not secure. */ -"Menu.EnhancedTrackingProtection.On.NotSecure.Header.v128" = "Vaše připojení není zabezpečené."; +"Menu.EnhancedTrackingProtection.On.NotSecure.Header.v128" = "Vaše spojení není zabezpečené."; /* Title for the enhanced tracking protection screen when the user has selected to be protected but the connection is not secure. */ "Menu.EnhancedTrackingProtection.On.NotSecure.Title.v128" = "Buďte na této stránce opatrní"; diff --git a/firefox-ios/Shared/Supporting Files/cs.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/cs.lproj/Toolbar.strings index 589f4a604ea16..ccaa1b4ff7145 100644 --- a/firefox-ios/Shared/Supporting Files/cs.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/cs.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hlavní nabídka"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zavřít tento panel"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Otevřené panely"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/EnhancedTrackingProtection.strings index 5779e26bf1f2c..a519a71b83afe 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Tracwyr wedi'u rhwystro: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Mae Safonol yn rhwystro tracwyr cyffredin ar ôl i dudalen ddechrau llwytho, felly efallai y byddwch chi'n gweld cyfrif traciwr uwch. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Mae Llym yn rhwystro mwy o dracwyr trwy eu hatal cyn i dudalen lwytho, felly efallai y byddwch chi'n gweld cyfrif traciwr is. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/cy.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/cy.lproj/Toolbar.strings index 39dbafafdcac3..d68fa490f1077 100644 --- a/firefox-ios/Shared/Supporting Files/cy.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/cy.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Prif Ddewislen"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Cau'r Tab Hwn"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tabiau ar agor"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/da.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/da.lproj/Toolbar.strings index 9ddbb39b6eb45..a7ae97cb00b3b 100644 --- a/firefox-ios/Shared/Supporting Files/da.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/da.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hovedmenu"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Luk dette faneblad"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Åbne faneblade"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/de.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/de.lproj/CustomizeFirefoxHome.strings index 194eef5bf2c64..e01e83b9a2aab 100644 --- a/firefox-ios/Shared/Supporting Files/de.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/de.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Meistgelesene Meldungen"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Weltmeisterschaft"; + diff --git a/firefox-ios/Shared/Supporting Files/de.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/de.lproj/EnhancedTrackingProtection.strings index 7553e3fadfd56..3ac1aab2304cb 100644 --- a/firefox-ios/Shared/Supporting Files/de.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/de.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blockierte Tracker: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blockiert gängige Skripte zur Aktivitätenverfolgung, nachdem eine Seite geladen wurde, sodass die Anzahl der Tracker möglicherweise höher ist. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Streng blockiert weitere Elemente zur Aktivitätenverfolgung, indem diese vor dem Laden einer Seite gestoppt werden, sodass es möglicherweise weniger Tracker gibt. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/de.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/de.lproj/Settings.strings index 307dda07eed63..2c452481f2098 100644 --- a/firefox-ios/Shared/Supporting Files/de.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/de.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Blockiert**: Sie werden die Funktion nicht sehen und können die Funktion nicht verwenden. Für die geräteinterne KI werden alle heruntergeladenen Modelle entfernt."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Blockiert**: Die Funktion wird nicht angezeigt und kann nicht verwendet werden. Für die geräteinterne KI werden alle heruntergeladenen Modelle entfernt."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Seiten und Zusammenfassungen werden nie gespeichert."; diff --git a/firefox-ios/Shared/Supporting Files/de.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/de.lproj/Toolbar.strings index c825c23a8ebeb..e413f981e6ce9 100644 --- a/firefox-ios/Shared/Supporting Files/de.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/de.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hauptmenü"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Diesen Tab schließen"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Offene Tabs"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/de.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/de.lproj/WorldCup.strings new file mode 100644 index 0000000000000..7be9755d0344c --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/de.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Länderauswahl für die WM schließen"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Afrika"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Asien"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Mittelamerika"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Nordamerika"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Ozeanien"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Südamerika"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "England"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Schottland"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Fertig"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Folgen Sie Ihrem Team"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Gruppe A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Gruppe B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Gruppe C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Gruppe D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Gruppe E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Gruppe F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Gruppe G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Gruppe H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Gruppe I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Gruppe J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Gruppe K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Gruppe L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Team ändern"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "T"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "St"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "Mi"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Countdown bis zur Weltmeisterschaft"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Benachrichtigungen deaktivieren"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Team auswählen"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Wählen Sie ein anderes Team, um es bei der Weltmeisterschaft zu verfolgen."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Das WM-Widget entfernen"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Entfernen"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Möchten Sie das Turnier trotzdem weiter verfolgen?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Wir konnten keine Spieldaten laden. Bitte aktualisieren."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Neuigkeiten zur Weltmeisterschaft ausblenden"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Folgen Sie Ihrem Team"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Erhalten Sie Live-Updates zu Spielen und mehr."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Behalten Sie die WM im Auge"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Spielende)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Benutzerdefiniertes Hintergrundbild herunterladen"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Verwandte Spiele"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "LIVE"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Entfernen"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Aktualisieren"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "SPIEL UM PLATZ 3"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FINALE"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Vollzeit • Strafen"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "VIERTELFINALE"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "16 RUDER"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "runde VON 32"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Scrollen Sie, um vorherige oder nächste Spiele zu sehen"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "HALBFINALE"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "In Kürze"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "WELTMEISTER 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Weitere Optionen"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Teilen"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Wir halten Sie auf dem Laufenden, wenn die Weltmeisterschaft näher rückt"; + diff --git a/firefox-ios/Shared/Supporting Files/dsb.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/dsb.lproj/CustomizeFirefoxHome.strings index f2199ea5c833e..9f8648015b837 100644 --- a/firefox-ios/Shared/Supporting Files/dsb.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/dsb.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Nejcesćej pśecytane tšojeńka"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Swětowe mejstaŕstwo"; + diff --git a/firefox-ios/Shared/Supporting Files/dsb.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/dsb.lproj/EnhancedTrackingProtection.strings index 6b92bd7aafecc..650fb3a46bb70 100644 --- a/firefox-ios/Shared/Supporting Files/dsb.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/dsb.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Pśeslědowaki blokěrowane: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard zwucone pśeslědowaki blokěrujo, za tym až bok chapja se zacytowaś, togodla móžośo wušu licbu pśeslědowakow wiźeś. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Striktny pśeslědowaki zastajijo, nježli až se bok zacytajo, aby wěcej pśeslědowakow blokěrował, togodla móžośo nišu licbu pśeslědowakow wiźeś. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/dsb.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/dsb.lproj/Settings.strings index b8c4c5bd92101..8f3fa5cd466bb 100644 --- a/firefox-ios/Shared/Supporting Files/dsb.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/dsb.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Blokěrowany**: Njebuźośo funkciju wiźeś a njamóžośo ju wužywaś. Za KI na rěźe se ześěgnjone modele wótwónoźuju."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Blokěrowany**: Njebuźośo funkciju wiźeś a njamóžośo ju wužywaś. Za KI na rěźe se ześěgnjone modele wótwónoźuju."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Boki a zespominanja se nigda njeskładuju."; diff --git a/firefox-ios/Shared/Supporting Files/dsb.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/dsb.lproj/Toolbar.strings index 2cdbca642f991..2270a9e6b71a9 100644 --- a/firefox-ios/Shared/Supporting Files/dsb.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/dsb.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Głowny meni"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Toś ten rejtarik zacyniś"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Wócynjone rejtariki"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/dsb.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/dsb.lproj/WorldCup.strings new file mode 100644 index 0000000000000..a0a5a9549df8e --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/dsb.lproj/WorldCup.strings @@ -0,0 +1,117 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Wuběrk krajow za swětowe mejstaŕstwo zacyniś"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Afrika"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Azija"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Srjejźna Amerika"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Pódpołnocna Amerika"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceaniska"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Pódpołdnjowa Amerika"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Engelska"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Šotiska"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Dokóńcony"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Wašomu teamoju slědowaś"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Kupka A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Kupka B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Kupka C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Kupka D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Kupka E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Kupka F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Kupka G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Kupka H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Kupka I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Kupka J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Kupka K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Kupka L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Team změniś"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "Ź"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "G"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Countdown k swětowemu mejstaŕstwoju"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Powěźeńki znjemóžniś"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Team wubraś"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Wubjeŕśo drugi team, aby wó swětowem mejstaŕstwje na běžnem wóstał."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Asistent swětowego mejstaŕstwa wótwónoźeś"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Wótwónoźeś"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Cośo dalej slědowaś?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Njejsmy mógli grajne daty zacytaś. Aktualizěrujśo pšosym."; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Wašomu teamoju slědowaś"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Wšen cas)"; + diff --git a/firefox-ios/Shared/Supporting Files/el.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/el.lproj/EnhancedTrackingProtection.strings index 849b6d7395be9..d35fc125e5836 100644 --- a/firefox-ios/Shared/Supporting Files/el.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/el.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Αποκλεισμένοι ιχνηλάτες: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Η τυπική λειτουργία αποκλείει τους συνήθεις ιχνηλάτες αφού ξεκινήσει η φόρτωση μιας σελίδας, επομένως ενδέχεται να δείτε μεγαλύτερο πλήθος ιχνηλατών. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Η αυστηρή λειτουργία αποκλείει περισσότερους ιχνηλάτες σταματώντας τους πριν από τη φόρτωση μιας σελίδας, επομένως ενδέχεται να δείτε μικρότερο πλήθος ιχνηλατών. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/el.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/el.lproj/Toolbar.strings index 484a44fbc39dc..7fa721c1b03ac 100644 --- a/firefox-ios/Shared/Supporting Files/el.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/el.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Κύριο μενού"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Κλείσιμο καρτέλας"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Ανοικτές καρτέλες"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/en-CA.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/en-CA.lproj/EnhancedTrackingProtection.strings index f05e2a9eabaf4..61f0ec5560c22 100644 --- a/firefox-ios/Shared/Supporting Files/en-CA.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/en-CA.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Trackers blocked: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blocks common trackers after a page starts loading, so you may see a higher tracker count. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strict blocks more trackers by stopping them before a page loads, so you may see a lower tracker count. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/en-CA.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/en-CA.lproj/Toolbar.strings index 641c7e42c3f65..8682cf3f5173a 100644 --- a/firefox-ios/Shared/Supporting Files/en-CA.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/en-CA.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Main Menu"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Close This Tab"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tabs open"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/en-GB.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/en-GB.lproj/EnhancedTrackingProtection.strings index 0221bff6a4997..dad48e297310b 100644 --- a/firefox-ios/Shared/Supporting Files/en-GB.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/en-GB.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Trackers blocked: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blocks common trackers after a page starts loading, so you may see a higher tracker count. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strict blocks more trackers by stopping them before a page loads, so you may see a lower tracker count. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/en-GB.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/en-GB.lproj/Toolbar.strings index b5ff9c535ccca..3b132a6efaa67 100644 --- a/firefox-ios/Shared/Supporting Files/en-GB.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/en-GB.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Main Menu"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Close This Tab"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tabs open"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/eo.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/eo.lproj/EnhancedTrackingProtection.strings index 38bceb45a4b0c..10358026f0724 100644 --- a/firefox-ios/Shared/Supporting Files/eo.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/eo.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blokitaj spuriloj: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La norma protekto blokas oftajn spurilojn post la komenco de ŝargado de paĝo, do vi povus konstati pli grandan nombron de spuriloj. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La rigora protekto blokas pli da spuriloj ĉar ĝi haltigas ilin antaŭ la paĝo komencas ŝargiĝi, do vi povus konstati pli etan nombron de spuriloj. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/eo.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/eo.lproj/Toolbar.strings index 152dbb1b8240f..bc7b998a9f6c0 100644 --- a/firefox-ios/Shared/Supporting Files/eo.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/eo.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Ĉefa menuo"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Fermi tiun ĉi langeton"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Malfermitaj langetoj"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/es-AR.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/es-AR.lproj/CustomizeFirefoxHome.strings index 0a75d1224ea6b..03316d07f755e 100644 --- a/firefox-ios/Shared/Supporting Files/es-AR.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/es-AR.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Historias principales"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Copa del Mundo"; + diff --git a/firefox-ios/Shared/Supporting Files/es-AR.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/es-AR.lproj/EnhancedTrackingProtection.strings index b22dae585d966..641ff443b7b5f 100644 --- a/firefox-ios/Shared/Supporting Files/es-AR.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/es-AR.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Estándar bloquea los rastreadores comunes después de que una página comienza a cargarse, por lo que podés ver un mayor número de rastreadores. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Estricto bloquea más rastreadores deteniéndolos antes de que se cargue una página, por lo que podés ver una cantidad menor de rastreadores. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/es-AR.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/es-AR.lproj/Settings.strings index 02aa57d6309a3..a09aeb9fa9233 100644 --- a/firefox-ios/Shared/Supporting Files/es-AR.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/es-AR.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Bloqueada**: no verás y no podrás usar la función. Para la IA en el dispositivo, se eliminan todos los modelos descargados."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Bloqueado**: No verás y no podrás usar la función. Para la IA en el dispositivo, se eliminan todos los modelos descargados."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Las páginas y los resúmenes nunca se almacenan."; diff --git a/firefox-ios/Shared/Supporting Files/es-AR.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/es-AR.lproj/Toolbar.strings index 0d8a36b779492..a6bbfa7f5c559 100644 --- a/firefox-ios/Shared/Supporting Files/es-AR.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/es-AR.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Cerrar esta pestaña"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Pestañas abiertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/es-AR.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/es-AR.lproj/WorldCup.strings new file mode 100644 index 0000000000000..ffd29e0ce1b10 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/es-AR.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Cerrar selector de países del Mundial"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "África"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Asia"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "América Central"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "América del Norte"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceanía"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "América del Sur"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Inglaterra"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Escocia"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Listo"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Seguir a tu equipo"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Grupo A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Grupo B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Grupo C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Grupo D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Grupo E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Grupo F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Grupo G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Grupo H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Grupo I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Grupo J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Grupo K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Grupo L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Cambiar equipo"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "D"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "H"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Cuenta regresiva para la Copa del Mundo"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Deshabilitar notificaciones"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Seleccionar equipo"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Elegí otro equipo para estar al día con la Copa del Mundo."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Eliminar el widget de la Copa Mundial"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Eliminar"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "¿Aún querés seguir a Along?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "No pudimos cargar los datos de coincidencias. Actualizar."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Ocultar actualizaciones de la Copa Mundial"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Seguir a tu equipo"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Conseguí actualizaciones de partidos en vivo y más."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Mantenete al tanto de la Copa del Mundo"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Tiempo completo)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Obtener un fondo de pantalla personalizado"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Partidos relacionados"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "EN VIVO"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Eliminar"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Refrescar"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "BRONCE FINAL"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FINAL"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Tiempo completo • Sanciones"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "CUARTOS DE FINAL"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "RONDA DE 16"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "RONDA DE 32"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Desplazar para ver coincidencias anteriores o siguientes"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "SEMIFINALES"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "Próximo"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "CAMPEONES DE LA COPA MUNDIAL 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Más opciones"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Compartir"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Te mantendremos informado a medida que se acerque la Copa del Mundo"; + diff --git a/firefox-ios/Shared/Supporting Files/es-CL.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/es-CL.lproj/EnhancedTrackingProtection.strings index 12672cd0a6be8..9c0ac43875bee 100644 --- a/firefox-ios/Shared/Supporting Files/es-CL.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/es-CL.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "El nivel estándar bloquea los rastreadores comunes después de que una página comienza a cargarse, por lo que es posible que veas un recuento de rastreadores más alto. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "El nivel estricto bloquea más rastreadores al detenerlos antes de que se cargue una página, por lo que es posible que veas un recuento de rastreadores menor. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/es-CL.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/es-CL.lproj/Toolbar.strings index c505380955cb7..9b284340aa4e0 100644 --- a/firefox-ios/Shared/Supporting Files/es-CL.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/es-CL.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Cerrar esta pestaña"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Pestañas abiertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/es-ES.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/es-ES.lproj/EnhancedTrackingProtection.strings index 0d745bb0ba29d..a8ba97bcaed12 100644 --- a/firefox-ios/Shared/Supporting Files/es-ES.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/es-ES.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La protección estándar bloquea los rastreadores comunes después de que una página comienza a cargarse, por lo que es posible que veas un recuento de rastreadores más alto. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La protección estricta bloquea más rastreadores al detenerlos antes de que se cargue una página, por lo que es posible que veas un recuento de rastreadores menor. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/es-ES.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/es-ES.lproj/Toolbar.strings index 790131017b456..487be73fffe0c 100644 --- a/firefox-ios/Shared/Supporting Files/es-ES.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/es-ES.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Cerrar esta pestaña"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Pestañas abiertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/es-MX.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/es-MX.lproj/EnhancedTrackingProtection.strings index 04e838c73859a..9bdd44f821200 100644 --- a/firefox-ios/Shared/Supporting Files/es-MX.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/es-MX.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "El estándar bloquea los rastreadores comunes después de que una página comienza a cargarse, por lo que es posible que veas un recuento de rastreadores más alto. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Los bloques estrictos bloquean más rastreadores al detenerlos antes de que se cargue una página, por lo que es posible que veas un recuento de rastreadores menor. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/es-MX.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/es-MX.lproj/Toolbar.strings index 8f3fd0caf5fd6..89664d73311ff 100644 --- a/firefox-ios/Shared/Supporting Files/es-MX.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/es-MX.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Cerrar esta pestaña"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Pestañas abiertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/eu.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/eu.lproj/EnhancedTrackingProtection.strings index 786d6dde07d01..d82041be0022e 100644 --- a/firefox-ios/Shared/Supporting Files/eu.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/eu.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blokeatutako jarraipen-elementuak: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Oinarrizkoak ohiko jarraipen-elementuak blokeatzen ditu orria kargatzen hasten den unetik aurrera, beraz jarraipen-elementuen kopuru handiagoa ikus zenezake. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Zorrotzak jarraipen-elementu gehiago blokeatzen ditu orria kargatzen hasi aurretik, beraz jarraipen-elementuen kopuru txikiagoa ikus zenezake. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/eu.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/eu.lproj/Toolbar.strings index c4734bd3f6d57..eeacca5de0f2b 100644 --- a/firefox-ios/Shared/Supporting Files/eu.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/eu.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu nagusia"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Itxi fitxa"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Irekitako fitxak"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/fi.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/fi.lproj/EnhancedTrackingProtection.strings index 247254a96f28c..c3aa9a81fac87 100644 --- a/firefox-ios/Shared/Supporting Files/fi.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/fi.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Seuraimia estetty: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Tavallinen estää yleiset seuraimet, kun sivu alkaa latautua, joten saatat nähdä suuremman seurainten määrän. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Tiukka estää enemmän seuraimia pysäyttämällä ne ennen sivun latautumista, joten seurainten määrä voi olla pienempi. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/fi.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/fi.lproj/Toolbar.strings index 02dc5611a9fa6..6a00bf1e7b715 100644 --- a/firefox-ios/Shared/Supporting Files/fi.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/fi.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Päävalikko"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Sulje välilehti"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Avoimet välilehdet"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/fr.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/fr.lproj/CustomizeFirefoxHome.strings index c8a36e980b622..a188290fcd4ae 100644 --- a/firefox-ios/Shared/Supporting Files/fr.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/fr.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Articles populaires"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Coupe du monde"; + diff --git a/firefox-ios/Shared/Supporting Files/fr.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/fr.lproj/EnhancedTrackingProtection.strings index a91ae1d788902..7153cd606cad5 100644 --- a/firefox-ios/Shared/Supporting Files/fr.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/fr.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Traqueurs bloqués : %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La protection standard bloque les traqueurs courants au chargement des pages, vous pouvez donc constater un nombre élevé de traqueurs. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La protection stricte bloque davantage de traqueurs en les arrêtant avant le chargement des pages. Vous constaterez peut-être un nombre moins élevé de traqueurs. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/fr.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/fr.lproj/Settings.strings index 5d3003a9c540a..46dd585a2adf1 100644 --- a/firefox-ios/Shared/Supporting Files/fr.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/fr.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Bloqué** : vous ne verrez pas et ne pourrez pas utiliser cette fonctionnalité. Pour l’IA locale, tous les modèles téléchargés sont supprimés."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Bloqué** : vous ne verrez pas et ne pourrez pas utiliser cette fonctionnalité. Pour l’IA sur l’appareil, tous les modèles téléchargés sont supprimés."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Les pages et les résumés ne sont jamais stockés."; diff --git a/firefox-ios/Shared/Supporting Files/fr.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/fr.lproj/Toolbar.strings index ada212b9650b4..e695323a18349 100644 --- a/firefox-ios/Shared/Supporting Files/fr.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/fr.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Fermer cet onglet"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Onglets ouverts"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/fr.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/fr.lproj/WorldCup.strings new file mode 100644 index 0000000000000..885a4b9718cce --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/fr.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Fermer le sélecteur de pays pour la Coupe du monde"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Afrique"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Asie"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Amérique centrale"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europe"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Amérique du Nord"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Océanie"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Amérique du Sud"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Angleterre"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Écosse"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Terminé"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Suivez votre équipe"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Groupe A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Groupe B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Groupe C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Groupe D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Groupe E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Groupe F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Groupe G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Groupe H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Groupe I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Groupe J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Groupe K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Groupe L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Changer d’équipe"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "j"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "h"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "m"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Compte à rebours avant la Coupe du monde"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Désactiver les notifications"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Choisir l’équipe"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Choisissez une autre équipe pour continuer à suivre la Coupe du monde."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Supprimer le widget Coupe du monde"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Supprimer"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Voulez-vous continuer à suivre la compétition ?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Nous n’avons pas pu charger les données du match. Veuillez actualiser."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Masquer les informations sur la Coupe du monde"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Suivez votre équipe"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Recevez les dernières infos en direct sur les matchs et bien plus encore."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Gardez un œil sur la Coupe du monde"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Match terminé)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Obtenir un fond d’écran personnalisé"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Matchs liés"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "EN DIRECT"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Supprimer"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Actualiser"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "PETITE FINALE"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FINALE"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Match terminé • Tirs au but"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "QUARTS DE FINALE"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "8ES DE FINALE"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "16ES DE FINALE"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Faites défiler pour voir les matchs précédents ou suivants"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "DEMI-FINALES"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "À venir"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "CHAMPIONS DU MONDE 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Options supplémentaires"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Partager"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Nous vous tiendrons au courant à l’approche de la Coupe du monde"; + diff --git a/firefox-ios/Shared/Supporting Files/gd.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/gd.lproj/EnhancedTrackingProtection.strings index 40733a4f43928..08646efe7cc66 100644 --- a/firefox-ios/Shared/Supporting Files/gd.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/gd.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Tracaichean air am bacadh: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Bacaidh an roghainn bhunaiteach tracaichean an dèidh toiseach luchdadh duilleige agus ri linn sin, dh’fhaodte gum faic thu àireamh nas motha de thracaichean. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Bacaidh an roghainn theann barrachd thracaichean le bhith a’ cur stad orra mus tèid duilleag a luchdadh agus ri linn sin agus dh’fhaodte gum faic thu àireamh nas lugha de thracaichean. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/gd.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/gd.lproj/Toolbar.strings index f89658bdc17df..85d564ba783f5 100644 --- a/firefox-ios/Shared/Supporting Files/gd.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/gd.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Am prìomh-chlàr-taice"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Dùin an taba seo"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tabaichean a tha fosgailte"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/gl.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/gl.lproj/Toolbar.strings index 52fe53ad86bc8..e0440c5be9ad3 100644 --- a/firefox-ios/Shared/Supporting Files/gl.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/gl.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menú principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -7,7 +7,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Pechar esta lapela"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Lapelas abertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/he.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/he.lproj/EnhancedTrackingProtection.strings index 9f2948a8f3760..a494a20988e6a 100644 --- a/firefox-ios/Shared/Supporting Files/he.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/he.lproj/EnhancedTrackingProtection.strings @@ -76,10 +76,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "רכיבי מעקב שנחסמו: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "מצב רגיל חוסם רכיבי מעקב נפוצים לאחר שדף מתחיל להיטען, כך שייתכן שתוצג ספירה גבוהה יותר של רכיבי מעקב. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "מצב מחמיר חוסם יותר רכיבי מעקב בכך שהוא עוצר אותם לפני שהדף נטען, כך שייתכן שתוצג ספירה נמוכה יותר של רכיבי מעקב. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/he.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/he.lproj/Toolbar.strings index 59798fa883e2e..a25d257350ae2 100644 --- a/firefox-ios/Shared/Supporting Files/he.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/he.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "תפריט ראשי"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "סגירת לשונית זו"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "לשוניות פתוחות"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/hr.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/hr.lproj/EnhancedTrackingProtection.strings index 01ccced92a152..3b8825e480e5e 100644 --- a/firefox-ios/Shared/Supporting Files/hr.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/hr.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blokirani programi za praćenje: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "„Standardno” blokira uobičajene alate za praćenje nakon što se stranica počne učitavati, stoga možeš vidjeti veći broj programa za praćenje. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "„Strogo” blokira više programa za praćenje zaustavljajući ih prije učitavanja stranice, stoga možeš vidjeti manji broj programa za praćenje. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/hr.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/hr.lproj/Toolbar.strings index aff0401b3313d..003f74da22a22 100644 --- a/firefox-ios/Shared/Supporting Files/hr.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/hr.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Glavni izbornik"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zatvori ovu karticu"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Otvorene kartice"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/hsb.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/hsb.lproj/CustomizeFirefoxHome.strings index c6d13ff1b4ec7..7773807d91c33 100644 --- a/firefox-ios/Shared/Supporting Files/hsb.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/hsb.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Najhusćišo přečitane zdźělenki"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Swětowe mišterstwo"; + diff --git a/firefox-ios/Shared/Supporting Files/hsb.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/hsb.lproj/EnhancedTrackingProtection.strings index 95a3a29eb47fc..b6581e171de00 100644 --- a/firefox-ios/Shared/Supporting Files/hsb.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/hsb.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Přesćěhowaki zablokowane: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard zwučene přesćěhowaki blokuje, po tym zo strona poča so začitować, tohodla móžeće wyšu ličbu přesćěhowakow widźeć. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Striktny přesćěhowaki zastaji, prjedy hač so strona začita, zo by wjace přesćěhowakow blokěrował, tohodla móžeće nišu ličbu přesćěhowakow widźeć. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/hsb.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/hsb.lproj/Settings.strings index fbece07e0a794..a433f87503b69 100644 --- a/firefox-ios/Shared/Supporting Files/hsb.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/hsb.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Zablokowany**: Njebudźeće funkciju widźeć a njemóžeće ju wužiwać. Za KI na graće so sćehnjene modele wotstronjeja."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Zablokowany**: Njebudźeće funkciju widźeć a njemóžeće ju wužiwać. Za KI na graće so sćehnjene modele wotstronjeja."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Strony a zjeća so ženje njeskładuja."; diff --git a/firefox-ios/Shared/Supporting Files/hsb.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/hsb.lproj/Toolbar.strings index 5aa3b90cf1595..f5310ed780284 100644 --- a/firefox-ios/Shared/Supporting Files/hsb.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/hsb.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hłowny meni"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Tutón rajtark začinić"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Wočinjene rajtarki"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/hsb.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/hsb.lproj/WorldCup.strings new file mode 100644 index 0000000000000..0d962522fe6b6 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/hsb.lproj/WorldCup.strings @@ -0,0 +1,177 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Wuběr krajow za swětowe mišterstwo začinić"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Afrika"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Azija"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Srjedźna Amerika"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Sewjerna Amerika"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceaniska"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Južna Amerika"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Jendźelska"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Šotiska"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Hotowo"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Wašemu teamej slědować"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Skupina A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Skupina B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Skupina C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Skupina D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Skupina E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Skupina F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Skupina G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Skupina H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Skupina I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Skupina J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Skupina K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Skupina L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Team změnić"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "D"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "H"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Countdown k swětowemu mišterstwu"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Zdźělenki znjemóžnić"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Team wubrać"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Wubjerće druhi team, zo byšće wo swětowym mišterstwje na běžnym wostał."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Asistent swětoweho mišterstwa wotstronić"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Wotstronić"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Chceće dale slědować?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Njemóžachmy hrajne daty začitać. Aktualizujće prošu."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Aktualizacija swětoweho mišterstwa schować"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Wašemu teamej slědować"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Dóstańće aktualizacije live a wjace."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Wobchowajće swětowe mišterstwo we wóčku"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Wšón čas)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Wobstarajće sej swójski pozadkowy wobraz"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Podobne hry"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "LIVE"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Wotstronić"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Aktualizować"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "HRA WO TŘEĆE MĚSTNO"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FINALE"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Wšón čas • Pokutne kopy"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "ŠTWÓRĆFINALE"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "KOŁO Z 16"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "KOŁO Z 32"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Kulće, zo byšće předchadne abo přichodne hry widźał"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "POŁFINALE"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "Přichodne"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "SWĚTOWI MIŠTROJO 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Dalše nastajenja"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Dźělić"; + diff --git a/firefox-ios/Shared/Supporting Files/hu.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/hu.lproj/EnhancedTrackingProtection.strings index d9d4a95d6b43c..052d50eb68a00 100644 --- a/firefox-ios/Shared/Supporting Files/hu.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/hu.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blokkolt követők: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "A Szokásos blokkolja a gyakori követőket az oldal betöltésének megkezdése után, így magasabb követőszámot láthat. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "A Szigorú több nyomkövetőt blokkol még az oldal betöltése előtt, így lehet, hogy kevesebb követőt fog látni. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/hu.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/hu.lproj/Toolbar.strings index d4e350e118367..eb3616ae4d635 100644 --- a/firefox-ios/Shared/Supporting Files/hu.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/hu.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Főmenü"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Lap bezárása"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Megnyitott lapok"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/EnhancedTrackingProtection.strings index 6a3b9f5988189..769dff29205cc 100644 --- a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Հետագծողներն արգելափակված են՝ %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Ստանդարտն արգելափակում է տարածված հետագծողներին էջը բեռնելուց հետո, ուստի կարող եք տեսնել հետագծողների ավելի բարձր քանակ։ %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Խիստն արգելափակում է ավելի շատ հետագծողներ՝ կանգնեցնելով դրանք մինչև էջի բեռնումը, ուստի դուք կարող եք տեսնել հետագծողների ավելի ցածր քանակ։ %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Settings.strings index b22e29fc48eb1..2cb9696d2ab88 100644 --- a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "Թարգմանելիս ընտրեք այս լեզուներից մեկը։"; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Ջնջել"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Նախընտրելի լեզուներ"; diff --git a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Toolbar.strings index 8c1072e654aa7..2d630404d8456 100644 --- a/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/hy-AM.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Հիմնական ցանկ"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Փակել այս ներդիրը"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Ներդիրները բաց են"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ia.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ia.lproj/EnhancedTrackingProtection.strings index dca79cd88ded5..d9dad753bbf2a 100644 --- a/firefox-ios/Shared/Supporting Files/ia.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ia.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Traciatores blocate: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La protection standard bloca le traciatores currente post que un pagina initia cargar, tu pote pois constatar un numero elevate de traciatores. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La protection stricte bloca plus de traciatores stoppante los ante cargar paginas, assi tu potera vider un numero inferior de traciatores. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ia.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ia.lproj/Toolbar.strings index 39259b5e8e64f..d969c0a32eefa 100644 --- a/firefox-ios/Shared/Supporting Files/ia.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ia.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Clauder iste scheda"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Schedas aperte"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/id.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/id.lproj/EnhancedTrackingProtection.strings index fc005b77cbf0b..bdc734576f285 100644 --- a/firefox-ios/Shared/Supporting Files/id.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/id.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Pelacak diblokir: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standar memblokir pelacak umum setelah laman mulai dimuat, sehingga Anda mungkin melihat cacah pelacak yang lebih tinggi. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Ketat memblokir lebih banyak pelacak dengan menghentikannya sebelum laman dimuat, sehingga Anda mungkin melihat cacah pelacak yang lebih rendah. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/id.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/id.lproj/Toolbar.strings index 8d5cb51e932a7..1ee77d35b3c87 100644 --- a/firefox-ios/Shared/Supporting Files/id.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/id.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu Utama"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Tutup Tab Ini"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tab terbuka"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/is.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/is.lproj/EnhancedTrackingProtection.strings index 8811cb51d29a7..97c017e198cfb 100644 --- a/firefox-ios/Shared/Supporting Files/is.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/is.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Lokað á rekjara: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Staðlað lokar fyrir algenga rekjara eftir að síða byrjar að hlaðast, þannig að þú gætir séð meiri fjölda rekjara. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strangt lokar á fleiri rekjara með því að stöðva þá áður en síða hleðst inn, þannig að þú gætir séð minni fjölda rekjara. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/is.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/is.lproj/Toolbar.strings index dfeb58191aab8..0d1116f6a501a 100644 --- a/firefox-ios/Shared/Supporting Files/is.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/is.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Aðalvalmynd"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Loka þessum flipa"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Opnir flipar"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/it.lproj/CustomizeFirefoxHome.strings index cfecc9663fe6c..5d3d14d8c1ba6 100644 --- a/firefox-ios/Shared/Supporting Files/it.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/it.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Storie principali"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Coppa del mondo"; + diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/it.lproj/EnhancedTrackingProtection.strings index ddf2c6238d775..1b8d0e4c0e987 100644 --- a/firefox-ios/Shared/Supporting Files/it.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/it.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Elementi traccianti bloccati: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La modalità normale blocca gli elementi traccianti più comuni dopo l’inizio del caricamento della pagina, quindi potresti visualizzare un numero maggiore di elementi traccianti. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La modalità restrittiva blocca più elementi traccianti bloccandoli prima del caricamento della pagina, quindi potresti vedere un numero di elementi traccianti inferiore. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/it.lproj/Settings.strings index 9f15ed92c6eff..c3c2dfee26d30 100644 --- a/firefox-ios/Shared/Supporting Files/it.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/it.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Bloccata**: la funzione non verrà visualizzata e non sarà possibile utilizzarla. Per l’IA sul dispositivo, tutti i modelli scaricati saranno rimossi."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Bloccata**: la funzione non verrà visualizzata e non sarà possibile utilizzarla. Per l’IA sul dispositivo, tutti i modelli scaricati saranno rimossi."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Le pagine e i riassunti non vengono mai salvati."; diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/it.lproj/Toolbar.strings index a8840e18d71e1..79e715de261cf 100644 --- a/firefox-ios/Shared/Supporting Files/it.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/it.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principale"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Chiudi questa scheda"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Schede aperte"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/it.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/it.lproj/WorldCup.strings new file mode 100644 index 0000000000000..7230744ddb970 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/it.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Chiudi il selettore delle nazioni per i Mondiali"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Africa"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Asia"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Centro America"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Nord America"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceania"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Sud America"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Inghilterra"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Scozia"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Fatto"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Segui la tua squadra"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Gruppo A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Gruppo B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Gruppo C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Gruppo D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Gruppo E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Gruppo F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Gruppo G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Gruppo H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Gruppo I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Gruppo J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Gruppo K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Gruppo L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Cambia squadra"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "G"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "O"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Conto alla rovescia per i Mondiali"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Disattiva notifiche"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Scegli squadra"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Scegli un’altra squadra per continuare a seguire i Mondiali."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Rimuovi il widget dei Mondiali"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Rimuovi"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Vuoi continuare a seguire il torneo?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Impossibile caricare i dati relativi alla partita. Prova ad aggiornare."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Nascondi aggiornamenti per i Mondiali"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Segui la tua squadra"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Ricevi aggiornamenti in tempo reale sulle partite e altro ancora."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Resta aggiornato sui Mondiali"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Fine partita)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Ottieni uno sfondo personalizzato"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Partite collegate"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "LIVE"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Rimuovi"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Aggiorna"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "FINALE 3° POSTO"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FINALE"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Fine partita • Rigori"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "QUARTI DI FINALE"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "OTTAVI DI FINALE"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "SEDICESIMI DI FINALE"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Scorri per visualizzare le corrispondenze precedenti o successive"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "SEMIFINALI"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "In arrivo"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "CAMPIONI DELLA COPPA DEL MONDO 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Altre opzioni"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Condividi"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Ti terremo aggiornato con l’avvicinarsi della Coppa del Mondo"; + diff --git a/firefox-ios/Shared/Supporting Files/ja.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ja.lproj/EnhancedTrackingProtection.strings index 5c1e81e4e06cf..4749eb3a1ca31 100644 --- a/firefox-ios/Shared/Supporting Files/ja.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ja.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "ブロックされた追跡: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "標準では、ページの読み込みが開始された後に一般的なトラッカーがブロックされるため、トラッカー数が多く表示される可能性があります。%@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "厳格では、ページが読み込まれる前にトラッカーを停止することでより多くのトラッカーをブロックするため、トラッカー数が少なく表示される可能性があります。%@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ja.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ja.lproj/Toolbar.strings index fc98a14b11fb4..352150c585a99 100644 --- a/firefox-ios/Shared/Supporting Files/ja.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ja.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "メインメニュー"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "このタブを閉じる"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "開いているタブ数"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ka.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ka.lproj/EnhancedTrackingProtection.strings index bb0fc08eb1bd0..a11499b2e5e04 100644 --- a/firefox-ios/Shared/Supporting Files/ka.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ka.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "შეზღუდული მეთვალყურეები: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "საშუალო ზღუდავს გავრცელებულ მეთვალყურეებს გვერდის ჩატვირთვის დაწყების შემდგომ, ამიტომ შეიძლება იხილოთ მეთვალყურეების მაღალი რიცხვი. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "მკაცრი ზღუდავს მეტ მეთვალყურეს მათი აღკვეთით გვერდის ჩატვირთვამდე, შედეგად შეიძლება იხილოთ მეთვალყურეების დაბალი რიცხვი. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ka.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/ka.lproj/Settings.strings index 24fa89fdddc9f..ce47293d00189 100644 --- a/firefox-ios/Shared/Supporting Files/ka.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/ka.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "აირჩიეთ ამ ენებიდან თარგმნისას."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "წაშლა"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "სასურველი ენები"; diff --git a/firefox-ios/Shared/Supporting Files/ka.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ka.lproj/Toolbar.strings index 7eec3b1def204..dea39c860af19 100644 --- a/firefox-ios/Shared/Supporting Files/ka.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ka.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "მთავარი მენიუ"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "ჩანართის დახურვა"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "გახსნილი ჩანართი"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/kab.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/kab.lproj/Toolbar.strings index 0e3272463d373..c0664cc09f999 100644 --- a/firefox-ios/Shared/Supporting Files/kab.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/kab.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Umuɣ agejdan"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Mdel iccer-a"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Accaren yeldin"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/kk.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/kk.lproj/EnhancedTrackingProtection.strings index 2051d358b3afc..a137d6bc193bb 100644 --- a/firefox-ios/Shared/Supporting Files/kk.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/kk.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Блокталған трекерлер: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "«Стандартты» режим бет жүктеле бастағаннан кейін жиі кездесетін трекерлерді блоктайды, сондықтан трекерлер санының көбейгенін байқауыңыз мүмкін. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "«Қатаң» режим көбірек трекерлерді бет жүктелгенге дейін тоқтату арқылы блоктайды, сондықтан трекерлер санының азайғанын байқауыңыз мүмкін. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/kk.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/kk.lproj/Toolbar.strings index 200fe1b3ea886..c8a63a343dc88 100644 --- a/firefox-ios/Shared/Supporting Files/kk.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/kk.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Басты мәзір"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Бұл бетті жабу"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Ашық беттер"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/km.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/km.lproj/Toolbar.strings index d05711384d997..6e3042a5e86c2 100644 --- a/firefox-ios/Shared/Supporting Files/km.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/km.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "ម៉ឺនុយដើម"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "បិទ​ផ្ទាំង​នេះ"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "ផ្ទាំងបើក"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ko.lproj/Bookmarks.strings b/firefox-ios/Shared/Supporting Files/ko.lproj/Bookmarks.strings index dddd2368132ae..5a4999a8ac916 100644 --- a/firefox-ios/Shared/Supporting Files/ko.lproj/Bookmarks.strings +++ b/firefox-ios/Shared/Supporting Files/ko.lproj/Bookmarks.strings @@ -61,6 +61,9 @@ /* The label displayed in the toast notification when saving a bookmark via the menu to a custom folder. %@ represents the custom name of the folder, created by the user, where the bookmark will be saved to. */ "Bookmarks.Menu.SavedBookmarkToastLabel.v136" = "“%@”에 저장됨"; +/* Placeholder text for the search field in the bookmarks panel, used to filter bookmarks by title or URL. */ +"Bookmarks.Search.Placeholder.v151" = "북마크 검색"; + /* The title for the Edit context menu action for sites in Home Panels */ "HomePanel.ContextMenu.Edit.v131" = "편집"; diff --git a/firefox-ios/Shared/Supporting Files/ko.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ko.lproj/EnhancedTrackingProtection.strings index 9d34fb8fecd15..62949f951f810 100644 --- a/firefox-ios/Shared/Supporting Files/ko.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ko.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "차단된 추적기: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "표준은 페이지가 로드되기 시작한 후 일반적인 추적기를 차단하므로 더 많은 추적기를 볼 수 있습니다. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "엄격함은 페이지가 로드되기 전에 중지하여 더 많은 추적기를 차단하므로, 더 적은 추적기 수가 표시될 수 있습니다. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ko.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/ko.lproj/Settings.strings index 33dfcc2ae96cd..1174cb67676f8 100644 --- a/firefox-ios/Shared/Supporting Files/ko.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/ko.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "번역할 때 다음 언어 중에서 선택하세요."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "삭제"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "선호 언어"; diff --git a/firefox-ios/Shared/Supporting Files/ko.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ko.lproj/Toolbar.strings index 70c2818d805e4..da66cffbec1e9 100644 --- a/firefox-ios/Shared/Supporting Files/ko.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ko.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "메인 메뉴"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "이 탭 닫기"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "열린 탭"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/lo.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/lo.lproj/Toolbar.strings index edaf4c0d3aa12..908c4642e8afd 100644 --- a/firefox-ios/Shared/Supporting Files/lo.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/lo.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "ເມນູຫຼັກ"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "ປິດແທັບນີ້"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "ແຖບເປີດ"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ml.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ml.lproj/Toolbar.strings index 8bc1dee2f5764..78d08f0ff2a42 100644 --- a/firefox-ios/Shared/Supporting Files/ml.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ml.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "പ്രധാന കുറിപ്പടിക്കട്ട"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "ഈ ടാബു് അടയ്ക്കുക"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "തുറന്ന ടാബുകൾ"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/EnhancedTrackingProtection.strings index 0d32d9eb39616..30b581a08fe95 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Sporere blokkert: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blokkerer vanlige sporere etter at en side begynner å lastes, så du kan se et høyere antall sporere. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Streng blokkerer flere sporere ved å stoppe dem før en side lastes, så du kan se et lavere antall sporere. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/nb.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/nb.lproj/Toolbar.strings index ad7c95b716a8e..c0cdbe7e70bd4 100644 --- a/firefox-ios/Shared/Supporting Files/nb.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/nb.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hovedmeny"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Lukk denne fanen"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Åpne faner"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/nl.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/nl.lproj/EnhancedTrackingProtection.strings index c31b5339d08c8..4c0a040d843c1 100644 --- a/firefox-ios/Shared/Supporting Files/nl.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/nl.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Trackers geblokkeerd: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standaard blokkeert veelgebruikte trackers nadat laden van een pagina is gestart, dus u ziet mogelijk meer trackers. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Streng blokkeert meer trackers door ze te stoppen voordat een pagina wordt geladen, dus u ziet mogelijk minder trackers. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/nl.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/nl.lproj/Toolbar.strings index 73f06839af568..c7996dba99273 100644 --- a/firefox-ios/Shared/Supporting Files/nl.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/nl.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hoofdmenu"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Dit tabblad sluiten"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Open tabbladen"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/nn.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/nn.lproj/EnhancedTrackingProtection.strings index 58f4de79ff45c..bb3a733ac236f 100644 --- a/firefox-ios/Shared/Supporting Files/nn.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/nn.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Sporarar blokkerte: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blokkerer vanlege sporarar etter at ei side byrjar å bli lasta, så du kan sjå fleire sporarar. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Streng blokkerer fleire sporarar ved å stoppe dei før ei side blir lasta, så du kan sjå færre sporarar. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/nn.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/nn.lproj/Toolbar.strings index 97f6ce1adfa93..e38833474e3a5 100644 --- a/firefox-ios/Shared/Supporting Files/nn.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/nn.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hovudmeny"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Lat att denne fana"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Opne faner"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/pa-IN.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/pa-IN.lproj/EnhancedTrackingProtection.strings index 9b8f632fef96f..c0888ef992714 100644 --- a/firefox-ios/Shared/Supporting Files/pa-IN.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/pa-IN.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "ਪਾਬੰਦੀ ਲਾਏ ਟਰੈਕਰ: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "ਸਟੈਂਡਰਡ ਸਫ਼ਾ ਲੋਡ ਹੋਣਾ ਸ਼ੁਰੂ ਹੋਣ ਦੇ ਬਾਅਦ ਆਮ ਟਰੈਕਰਾਂ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਉਂਦਾ ਹੈ, ਇਸਕਰਕੇ ਤੁਸੀਂ ਵੱਧ ਟਰੈਕਰਾਂ ਦੀ ਗਿਣਤੀ ਵੇਖ ਸਕਦੇ ਹੋ। %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "ਸਖ਼ਤ ਨਾਲ ਸਫ਼ਾ ਲੋਡ ਹੋਣ ਤੋਂ ਪਹਿਲਾਂ ਕੀ ਬਹੁਤ ਟਰੈਕਰਾਂ ਉੱਤੇ ਰੋਕ ਲਾਈ ਜਾਂਦੀ ਹੈ, ਇਸਕਰਕੇ ਤੁਸੀਂ ਘੱਟ ਟਰੈਕਰ ਗਿਣਤੀ ਵੇਖੋਗੇ। %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/pa-IN.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/pa-IN.lproj/Toolbar.strings index f0aa431b33aa3..08175fd756924 100644 --- a/firefox-ios/Shared/Supporting Files/pa-IN.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/pa-IN.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "ਮੁੱਖ ਮੇਨੂ"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "ਇਸ ਟੈਬ ਨੂੰ ਬੰਦ ਕਰੋ"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "ਟੈਨਾਂ ਨੂੰ ਖੋਲ੍ਹੋ"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/pl.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/pl.lproj/EnhancedTrackingProtection.strings index 4c89427052282..73861821f6684 100644 --- a/firefox-ios/Shared/Supporting Files/pl.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/pl.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Zablokowane elementy śledzące: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standardowa ochrona blokuje często występujące elementy śledzące po rozpoczęciu wczytywania strony, dlatego ich liczba może być większa. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Ścisła ochrona blokuje więcej elementów śledzących, zatrzymując je przed wczytaniem strony, dlatego ich liczba może być mniejsza. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/pl.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/pl.lproj/Toolbar.strings index 60c555ef3d593..75504198efac6 100644 --- a/firefox-ios/Shared/Supporting Files/pl.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/pl.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu główne"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zamknij tę kartę"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Otwarte karty"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/EnhancedTrackingProtection.strings index fb32706e1d8eb..5adf721049952 100644 --- a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "O modo normal bloqueia rastreadores comuns após cada página começar a ser carregada, então pode aparecer uma contagem maior de rastreadores. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "O modo rigoroso bloqueia mais rastreadores ao impedi-los antes do carregamento das páginas, então deve aparecer uma contagem menor de rastreadores. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Settings.strings index da1c9bd4c07f3..6a99efe109cb4 100644 --- a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "Escolha um destes idiomas para traduzir."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Excluir"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Idiomas preferidos"; diff --git a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Toolbar.strings index b963646c27e16..c6968b2901368 100644 --- a/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/pt-BR.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Fechar esta aba"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Abas abertas"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/CustomizeFirefoxHome.strings index 3ca0a31160819..dcac75bcc8050 100644 --- a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Principais histórias"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Taça do mundo"; + diff --git a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/EnhancedTrackingProtection.strings index 213c63fee5c84..3797639e76396 100644 --- a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Rastreadores bloqueados: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "O modo Predefinido bloqueia os rastreadores mais comuns, depois da página começar a ser carregada, pelo que pode ver um número de rastreadores mais elevado. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "O modo Rigoroso bloqueia mais rastreadores, parando os mesmos antes do carregamento da página, pelo que pode ver um número de rastreadores mais baixo. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Settings.strings index 0bd4d3f0fab07..e7a198e170894 100644 --- a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Bloqueada**: não poderá ver e não poderá utilizar a funcionalidade. Para a IA no dispositivo, quaisquer modelos transferidos são removidos."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "%Bloqueado**: Não poderá ver e não poderá utilizar a funcionalidade. Para IA no dispositivo, quaisquer modelos transferidos são removidos."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "As páginas e resumos nunca são guardados."; diff --git a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Toolbar.strings index 31fb4678574eb..38e2c730412f8 100644 --- a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Fechar este separador"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Separadores abertos"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/pt-PT.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/WorldCup.strings new file mode 100644 index 0000000000000..42dc115137334 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/pt-PT.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Fechar seletor do país para a copa do mundo"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "África"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Ásia"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "América Central"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "América do Norte"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceânia"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "América do Sul"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Inglês"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Escocês"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Feito"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Siga a sua equipa"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Grupo A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Grupo B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Grupo C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Grupo D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Grupo E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Grupo F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Grupo G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Grupo H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Grupo I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Grupo J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Grupo K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Grupo L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Mudar equipa"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "D"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "H"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Contagem regressiva para a copa do mundo"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Desativar notificações"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Escolher equipa"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Escolha outra equipa para acompanhar o copa do mundo."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Remover a widget do copa do mundo"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Remover"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Ainda quer seguir?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Não conseguimos carregar os dados da correspondência. Por favor, atualize."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Ocultar atualizações do título do mundo"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Siga a sua equipa"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Obtenha atualizações dos jogos em direto e muito mais."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Manter separadores no copa do mundo"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Tempo completo)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Obter um fundo personalizado"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Correspondências relacionadas"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "AO DIREITO"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Remover"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Atualizar"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "FInal Bronze"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "FInal"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Tempo completo • Sanções"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "QUARTOS-DE-Final"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "Rodada de 16"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "Rodada de 32"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Desloque para ver as correspondências anteriores ou seguintes"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "semi-finais"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "Brevemente"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "Campeões da copa do mundo de 2026"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Mais opções"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Partilhar"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Manter-lhe-emos atualizado(a) à medida que o copa do mundo se aproxima"; + diff --git a/firefox-ios/Shared/Supporting Files/rm.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/rm.lproj/EnhancedTrackingProtection.strings index 76fb612f14639..0d2df5e5a15dd 100644 --- a/firefox-ios/Shared/Supporting Files/rm.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/rm.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Fastizaders bloccads: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "La protecziun predefinida blochescha fastizaders frequents suenter ch’ina pagina cumenza a chargiar. Perquai vesas ti eventualmain in dumber pli aut da fastizaders. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "La protecziun stricta blochescha dapli fastizaders cun als fermar avant ch’ina pagina chargia. Perquai vesas ti eventualmain in dumber pli bass da fastizaders. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/rm.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/rm.lproj/Toolbar.strings index 74eafc850be8d..0607e645a10fb 100644 --- a/firefox-ios/Shared/Supporting Files/rm.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/rm.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Serrar quest tab"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Tabs averts"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ro.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ro.lproj/EnhancedTrackingProtection.strings index fb6e425d75d19..a49b7e7e021cd 100644 --- a/firefox-ios/Shared/Supporting Files/ro.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ro.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Elemente de urmărire blocate: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Funcția standard blochează elementele de urmărire frecvente după ce începe să se încarce o pagină, așa că este posibil să vezi un număr mai mare de elemente de urmărire. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Funcția strictă blochează mai multe elemente de urmărire oprindu-le înainte să se încarce pagina. așa că este posibil să vezi un număr mai mic de elemente de urmărire. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ro.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ro.lproj/Toolbar.strings index 198cbeb966efc..49ef9367a0e92 100644 --- a/firefox-ios/Shared/Supporting Files/ro.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ro.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Meniu principal"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Închide fila"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "File deschise"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ru.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ru.lproj/EnhancedTrackingProtection.strings index 853e701c5acbb..c143ca06f7ec0 100644 --- a/firefox-ios/Shared/Supporting Files/ru.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ru.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Заблокировано трекеров: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Стандартная блокирует распространённые трекеры после начала загрузки страницы, поэтому вы можете увидеть большее количество трекеров. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Строгая блокирует больше трекеров, останавливая их перед загрузкой страницы, поэтому вы можете увидеть меньшее количество трекеров. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ru.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ru.lproj/Toolbar.strings index d8001e507a640..6d49bc1b395af 100644 --- a/firefox-ios/Shared/Supporting Files/ru.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ru.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Основное меню"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Закрыть эту вкладку"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Открытых вкладок"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/scn.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/scn.lproj/Toolbar.strings index 92dcb57667644..23c12a2c7a7d6 100644 --- a/firefox-ios/Shared/Supporting Files/scn.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/scn.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Minù principali"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -7,7 +7,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Chiuji sta scheda"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Schedi graputi"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/si.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/si.lproj/Toolbar.strings index 999b6cf72e150..6445943c53cf2 100644 --- a/firefox-ios/Shared/Supporting Files/si.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/si.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "ප්‍රධාන වට්ටෝරුව"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ diff --git a/firefox-ios/Shared/Supporting Files/sk.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/sk.lproj/EnhancedTrackingProtection.strings index 24cd41d5761f4..9c74b3cc2f832 100644 --- a/firefox-ios/Shared/Supporting Files/sk.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/sk.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Zablokované sledovacie prvky: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Štandardná úroveň blokuje bežné sledovacie prvky po začatí načítavania stránky, čo môže viesť k vyššiemu počtu sledovacích prvkov. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Prísna úroveň blokuje bežné sledovacie prvky ešte pred načítaním stránky, čo vedie k nižšiemu počtu sledovacích prvkov. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/sk.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/sk.lproj/Settings.strings index 84a2cff7dc700..bc090e932ffe0 100644 --- a/firefox-ios/Shared/Supporting Files/sk.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/sk.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "Pri preklade si vyberte z týchto jazykov."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Odstrániť"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Preferované jazyky"; diff --git a/firefox-ios/Shared/Supporting Files/sk.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/sk.lproj/Toolbar.strings index 45783cd48f906..acd1b0d486cd4 100644 --- a/firefox-ios/Shared/Supporting Files/sk.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/sk.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Hlavná ponuka"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zavrieť túto kartu"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Otvorené karty"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/AppIconSelection.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/AppIconSelection.strings index d7ed2d6205675..c36ba56a60e30 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/AppIconSelection.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/AppIconSelection.strings @@ -56,7 +56,7 @@ "Settings.AppIconSelection.AppIconNames.NorthernLights.Title.v137" = "Severni sij"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the Firefox icon with a orange background. */ -"Settings.AppIconSelection.AppIconNames.Orange.Title.v137" = "Oranžno"; +"Settings.AppIconSelection.AppIconNames.Orange.Title.v137" = "Oranžna"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the Firefox icon with a pink background. */ "Settings.AppIconSelection.AppIconNames.Pink.Title.v136" = "Roza"; @@ -68,10 +68,10 @@ "Settings.AppIconSelection.AppIconNames.Pride.Title.v136" = "Ponos"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the Firefox icon with a purple background. */ -"Settings.AppIconSelection.AppIconNames.Purple.Title.v137" = "Vijolično"; +"Settings.AppIconSelection.AppIconNames.Purple.Title.v137" = "Vijolična"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the Firefox icon with a red background. */ -"Settings.AppIconSelection.AppIconNames.Red.Title.v137" = "Rdeče"; +"Settings.AppIconSelection.AppIconNames.Red.Title.v137" = "Rdeča"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the default Firefox for iOS icon. */ "Settings.AppIconSelection.AppIconNames.Regular.Title.v136" = "Privzeta"; @@ -95,7 +95,7 @@ "Settings.AppIconSelection.AppIconNames.Twilight.Title.v137" = "Mrak"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the name of the Firefox icon with a yellow background. */ -"Settings.AppIconSelection.AppIconNames.Yellow.Title.v137" = "Rumeno"; +"Settings.AppIconSelection.AppIconNames.Yellow.Title.v137" = "Rumena"; /* On the app icon customization screen where you can select an alternate icon for the app, this is the subtitle shown on alternative app icons added by contributors which credit them for their design work. The parameter %@ specifies the creator's name, @ handle, or other personal identifier. */ "Settings.AppIconSelection.ContributorCredit.Subtitle.v139" = "Ustvaril/-a %@"; diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/EnhancedTrackingProtection.strings index b24d4e8d6e690..47a8b46fe24ff 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Zavrnjenih sledilcev: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standardni način blokira običajne sledilce, potem ko se stran začne nalagati, zato je lahko prikazano večje število sledilcev. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strogi način blokira več sledilcev, tako da jih zaustavi še pred naložitvijo strani. Prikazano število sledilcev je zato lahko manjše. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/Onboarding.strings index c0319b3af7ba6..a4547d45bf27c 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/Onboarding.strings @@ -181,6 +181,12 @@ /* Title for the welcome card in the v148 brand refresh onboarding flow. */ "Onboarding.Modern.BrandRefresh.Welcome.Title.v148" = "Poslovite se od vsiljivih sledilcev"; +/* Title for the welcome card in the v148 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Welcome.Title.v148.v2" = "Odpirajte povezave z vgrajeno zasebnostjo"; + +/* Title for the welcome card in the v149 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Welcome.TitleV3.v149" = "Odpirajte povezave z vgrajeno zasebnostjo"; + /* Option for the automatic theme. */ "Onboarding.Modern.Customization.Theme.Automatic.v145" = "Samodejno"; diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/ReaderModeBar.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/ReaderModeBar.strings new file mode 100644 index 0000000000000..ef60450e81cdb --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/ReaderModeBar.strings @@ -0,0 +1,3 @@ +/* Accessibility label for the summarize button in the reader mode bar view */ +"ReaderModeBar.SummarizeButtonAccessibilityLabel.v150" = "Povzemi vsebino strani"; + diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/Settings.strings index 732f8cb6b0eea..c1eb28cec7211 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/Settings.strings @@ -64,6 +64,48 @@ /* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ "Settings.AddressAutofill.Title.v126" = "Naslovi"; +/* In the AI Controls settings, in the AI powered features section, this is the text that indicates the feature is turned on. */ +"Settings.AIControls.AIPoweredFeaturesSection.AvailableStatus.v151" = "Na voljo"; + +/* In the AI Controls settings, in the AI powered features section, this is the text that what the available status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.AvailableStatusDescription.v151" = "**Na voljo**: zmožnost bo prikazana in jo boste lahko uporabljali."; + +/* In the AI Controls settings, in the AI powered features section, this is the text that indicates the feature is turned off. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatus.v151" = "Prepovedano"; + +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Prepovedano**: zmožnost se vam ne bo prikazovala in je ne boste mogli uporabljati. Morebitni modeli UI, ki so se že prenesli na napravo, bodo odstranjeni."; + +/* In the AI Controls settings, in the AI powered features section, this is the title that describes the page summaries feature */ +"Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Title.v151" = "Povzetki strani"; + +/* In the AI Controls settings, in the AI powered features section, this is the message that describes the translation feature */ +"Settings.AIControls.AIPoweredFeaturesSection.TranslationSection.Message.v151" = "Prevodi ostanejo zasebni na vaši napravi."; + +/* In the AI Controls settings, in the AI powered features section, this is the title that describes the translation feature */ +"Settings.AIControls.AIPoweredFeaturesSection.TranslationSection.Title.v151" = "Prevajanje"; + +/* This is the description for the setting that toggles whether to block AI enhancements under the AI Controls settings section. %@ is the app name (e.g. Firefox). */ +"Settings.AIControls.BlockAIEnhancementsDescription.v151" = "Prepoved pomeni, da %@ ne bo prikazoval novih ali trenutnih izboljšav z uporabo umetne inteligence, niti obvestil v zvezi z njimi."; + +/* In the AI Controls settings, this is the text for the link that takes the user more information on AI features. */ +"Settings.AIControls.BlockAIEnhancementsLink.v151" = "Kaj je in kaj ni vključeno"; + +/* This is the title for the setting that toggles whether to block AI enhancements under the AI Controls settings section. */ +"Settings.AIControls.BlockAIEnhancementsTitle.v151" = "Prepovej izboljšave s pomočjo UI"; + +/* In the AI Controls settings, this is the text that appears when you have turned on the Block AI Enhancements toggle */ +"Settings.AIControls.BlockedInformation.v151" = "Nove in trenutne izboljšave z uporabo UI so privzeto prepovedane. Določene možnosti lahko posebej omogočite spodaj."; + +/* This is the text for the link in the Header Card under the AI Controls settings section that links to more information. */ +"Settings.AIControls.HeaderCard.Link.v151" = "Več o tem"; + +/* This is the title for the Header Card under the AI Controls settings section. %@ is the app name (e.g. Firefox). */ +"Settings.AIControls.HeaderCard.Title.v151" = "V %@u imate vedno izbiro"; + +/* In the settings menu, in the General section, this is the title for the AI Controls settings section */ +"Settings.AIControls.Title.v150" = "Nadzor UI"; + /* Section description in Appearance settings for middle button configuration of the bottom toolbar. */ "Settings.Appearance.NavigationToolbar.Description.v145" = "Zamenja sredinski gumb v orodni vrstici."; @@ -256,6 +298,21 @@ /* Label used as a toggle item in Settings. When this is off, the user is opting out of all studies. */ "Settings.Studies.Title.v136" = "Nameščaj in izvajaj raziskave"; +/* This is the footer text for the setting that toggles the Summarize feature under the Summarize settings section. */ +"Settings.Summarize.FooterTitle.v142" = "Omogoča dostop do povzemanja vsebine strani."; + +/* This is the footer text for the gestures features under the Summarize settings section. */ +"Settings.Summarize.GesturesSection.FooterTitle.v142" = "Potresite napravo, da povzamete vsebino strani."; + +/* This is the title for the setting that toggles the Shake Gesture feature under the Summarize settings section. */ +"Settings.Summarize.GesturesSection.ShakeGestureTitle.v142" = "Potresite za povzetek"; + +/* This is the section title for the gestures features under the Summarize settings section. */ +"Settings.Summarize.GesturesSection.Title.v142" = "Poteze"; + +/* The accessibility label for the language picker button in the Summarize settings. */ +"Settings.Summarize.LanguageSection.PickerButtonAccessibilityLabel.v149" = "Izberite jezik"; + /* The label for the picker option to select the preferred app language for the summarizer inside the Summarize settings. The preferred app language refers to the language that the user has selected to use for the app in the app settings. */ "Settings.Summarize.LanguageSection.PreferredAppLanguageLabel.v149" = "Želeni jezik aplikacije"; @@ -265,6 +322,12 @@ /* The label for the picker option to select the website language for the summarizer inside the Summarize settings. */ "Settings.Summarize.LanguageSection.WebsiteLanguageLabel.v149" = "Jezik spletnih strani"; +/* This is the title for the setting that toggles the Summarize feature under the Summarize settings section. */ +"Settings.Summarize.SummarizePagesTitle.v142" = "Povzemaj vsebino strani"; + +/* In the settings menu, in the General section, this is the title for the Summarize settings section. */ +"Settings.Summarize.Title.v142" = "Povzetki strani"; + /* Title for a link that explains how Mozilla send technical and interaction data. */ "Settings.TechnicalData.Link.v136" = "Več o tem"; @@ -283,12 +346,30 @@ /* In the Appearance settings menu, in the Toolbar Button section, this label indicates that selecting this will make the New Tab button appear in the middle of the navigation toolbar. */ "Settings.Toolbar.Navigation.MiddleButton.NewTab.v145" = "Nov zavihek"; +/* Footer text below the auto-translate toggle in the Translation settings screen. */ +"Settings.Translation.AutoTranslate.Footer.v151" = "Samodejno prevede strani v vaš izbrani jezik."; + +/* Title for the auto-translate toggle in the Translation settings screen. */ +"Settings.Translation.AutoTranslate.Title.v151" = "Samodejno prevedi"; + +/* Navigation bar title of the language picker shown when adding a preferred translation language. */ +"Settings.Translation.LanguagePicker.NavTitle.v151" = "Izberi jezik"; + +/* Placeholder text for the search bar in the translation language picker. */ +"Settings.Translation.LanguagePicker.SearchPlaceholder.v151" = "Išči"; + /* Row label in the preferred languages list that opens the language picker to add a new preferred language for translation. */ "Settings.Translation.PreferredLanguages.AddLanguage.v151" = "Dodaj jezik …"; /* Subtitle on the device language row in the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.DeviceLanguage.v151" = "Jezik naprave"; +/* Footer text below the preferred languages list in the Translation settings screen. */ +"Settings.Translation.PreferredLanguages.Footer.v151" = "Pri prevajanju lahko izbirate med temi jeziki."; + +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Izbriši"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Prednostni jeziki"; diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/Summarize.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/Summarize.strings new file mode 100644 index 0000000000000..abf010d748425 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/Summarize.strings @@ -0,0 +1,3 @@ +/* Contextual hints are little popups that appear for the users informing them of new features. This is the description of one that points the user to the summarize button on the new toolbar layout. */ +"ContextualHints.Summarize.Description.v142" = "Tapnite za povzetek strani. Dotaknite se in pridržite za bralni pogled."; + diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/Summarizer.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/Summarizer.strings index 6aba2d536d697..529b75d668d46 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/Summarizer.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/Summarizer.strings @@ -1,6 +1,30 @@ +/* The label displayed in the summary report when the summary was generated using Apple Intelligence. */ +"Summarizer.AppleBrand.Label.v142" = "Povzeto z Apple Intelligence"; + +/* The a11y label for the close button in the summary view. */ +"Summarizer.CloseButton.Accessibility.Label.v145" = "Zapri povzetek"; + /* The label for the error button that allows the user to close the summary view because there is an error summarizing the page and the summary cannot be retried. */ "Summarizer.CloseButton.Label.v142" = "Zapri"; +/* The error message displayed when the summarizer encounters missing page content while summarizing a page (e.g the page is still loading). */ +"Summarizer.Error.MissingPageContent.Message.v142" = "Stran se še vedno nalaga. Počakajte, da se nalaganje konča, in nato pritisnite za povzetek."; + +/* The error message displayed when the summarizer encounters an unknown error while summarizing a page. */ +"Summarizer.Error.Unknown.Message.v142" = "Napaka pri povzemanju vsebine strani. Poskusite znova pozneje."; + +/* The error message displayed when the summarizer encounters unsafe content while summarizing a page. */ +"Summarizer.Error.UnsafeWebsite.Message.v142" = "Zaznana omejena vsebina. Ta stran ima morda omejeno dostopnost ali je vsebina večinoma slikovna."; + +/* The error message displayed when the summarizer encounters unsupported content while summarizing a page (e.g unsupported language, content too long). */ +"Summarizer.Error.UnsupportedContent.Message.v142" = "To spletno mesto ne dovoljuje povzemanja vsebine. Poskusite z drugo stranjo."; + +/* The description is displayed at the end of the summary report as a footnote to the users in that the report can contain errors. */ +"Summarizer.Footnote.Label.v144" = "Opomba: pri povzemanju lahko pride do napak."; + +/* The label displayed in the summary report when the summary was generated using by a third-party service. %@ refers to the name of the service/app (e.g Firefox). */ +"Summarizer.HostedBrand.Label.v142" = "Povzeto z uporabo %@"; + /* When the user uses the summarizing feature, this is the loading label that is shown while the summarization is being performed. */ "Summarizer.Loading.Label.v142" = "Povzemanje …"; @@ -13,3 +37,9 @@ /* The label for the accept button in the info panel in the summarizer when the user has not yet accepted the ToS. */ "Summarizer.ToS.InfoPanel.ContinueButton.Label.v143" = "Nadaljuj"; +/* The message for the Terms of Service alert that asks the user if they want to continue to summarize the page. %@ is the app name (e.g Firefox). */ +"Summarizer.ToS.InfoPanel.Label.v143" = "Oglejte si bistvo strani v nekaj sekundah s %@om."; + +/* The title for the Terms of Service information panel that asks the user if they want to summarize this page. */ +"Summarizer.ToS.InfoPanel.Title.Label.v143" = "Povzamem vsebino strani?"; + diff --git a/firefox-ios/Shared/Supporting Files/sl.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/sl.lproj/Toolbar.strings index 0a7c1f731c9d4..ef884b74c0394 100644 --- a/firefox-ios/Shared/Supporting Files/sl.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/sl.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Glavni meni"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Zapri zavihek"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Odprtih zavihkov"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/sq.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/sq.lproj/EnhancedTrackingProtection.strings index c718170f140eb..157d4c5d697bb 100644 --- a/firefox-ios/Shared/Supporting Files/sq.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/sq.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Gjurmues të bllokuar: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standardi bllokom gjurmues të rëndomtë pasi faqja fillon të ngarkohet, ndaj mund të shihni një numër më të lartë gjurmuesish. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strikti bllokon më tepër gjurmues, duke i ndaluar ata para se faqja të fillojë të ngarkohet, ndaj mund të shihni një numër më të ulët gjurmuesish. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/sq.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/sq.lproj/Toolbar.strings index f625e9055d057..0ec67c79bc1c4 100644 --- a/firefox-ios/Shared/Supporting Files/sq.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/sq.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menuja Kryesore"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Mbylle Këtë Skedë"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Skeda të hapura"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/sr.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/sr.lproj/EnhancedTrackingProtection.strings index 6160d512aea7b..4470a8cda079c 100644 --- a/firefox-ios/Shared/Supporting Files/sr.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/sr.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Блокирани пратиоци: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Стандардна блокира уобичајене пратиоце након што страница почне да се учитава, па можете видети већи број пратилаца. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Строга блокира више пратилаца тако што их зауставља пре него што се страница учита, па можете видети мањи број пратилаца. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/sr.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/sr.lproj/Toolbar.strings index a7fd0b162f447..8d3264b2932f6 100644 --- a/firefox-ios/Shared/Supporting Files/sr.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/sr.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Главни мени"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Затвори овај језичак"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Отворени језичци"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/sv.lproj/CustomizeFirefoxHome.strings b/firefox-ios/Shared/Supporting Files/sv.lproj/CustomizeFirefoxHome.strings index 321154e116e72..baaebccca33fd 100644 --- a/firefox-ios/Shared/Supporting Files/sv.lproj/CustomizeFirefoxHome.strings +++ b/firefox-ios/Shared/Supporting Files/sv.lproj/CustomizeFirefoxHome.strings @@ -13,3 +13,6 @@ /* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the Top Stories recommendations section on the Firefox homepage on or off */ "Settings.Home.Option.TopStories.v143" = "Populära nyheter"; +/* In the settings menu, in the Firefox homepage customization section, this is the title for the option that allows users to turn the World Cup Widget on the Firefox homepage on or off */ +"Settings.Home.Option.WorldCup.v151" = "Världscupen"; + diff --git a/firefox-ios/Shared/Supporting Files/sv.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/sv.lproj/EnhancedTrackingProtection.strings index 6fe85772796d4..5ecea826d3cc8 100644 --- a/firefox-ios/Shared/Supporting Files/sv.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/sv.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Blockerade spårare: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standard blockerar vanliga spårare efter att en sida har börjat laddas, så du kan se ett högre antal spårare. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Strikt blockerar fler spårare genom att stoppa dem innan en sida laddas, så du kan se ett lägre antal spårare. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/sv.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/sv.lproj/Settings.strings index a8f9426021173..4ea898c6b1c40 100644 --- a/firefox-ios/Shared/Supporting Files/sv.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/sv.lproj/Settings.strings @@ -76,6 +76,9 @@ /* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ "Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Blockerad**: du ser inte och kan inte använda funktionen. För enhetsbaserad AI tas alla hämtade modeller bort."; +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescriptionV2.v151" = "**Blockerad**: Du kommer inte att se och kan inte använda funktionen. För enhetsbaserad AI tas alla hämtade modeller bort."; + /* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ "Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Sidor och sammanfattningar lagras aldrig."; diff --git a/firefox-ios/Shared/Supporting Files/sv.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/sv.lproj/Toolbar.strings index c05889302b7d7..f24c90353573b 100644 --- a/firefox-ios/Shared/Supporting Files/sv.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/sv.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Huvudmeny"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Stäng denna flik"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Flikar öppna"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/sv.lproj/WorldCup.strings b/firefox-ios/Shared/Supporting Files/sv.lproj/WorldCup.strings new file mode 100644 index 0000000000000..393c9157bb95a --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/sv.lproj/WorldCup.strings @@ -0,0 +1,180 @@ +/* Accessibility label for the close button on the country picker for the World Cup widget. This allows users to close the country picker. */ +"WorldCup.CountryPicker.Close.AccessibilityLabel.v151" = "Stäng VM-landsväljaren"; + +/* Section header for the Africa region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Africa.v151" = "Afrika"; + +/* Section header for the Asia region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Asia.v151" = "Asien"; + +/* Section header for the Central America region in the World Cup country picker. Note: Central America falls under the CONCACAF confederation in FIFA. This string is included for completeness in case the UI groups countries by geographic region rather than confederation. */ +"WorldCup.CountryPicker.Confederation.CentralAmerica.v151" = "Centralamerika"; + +/* Section header for the CONCACAF confederation group in the World Cup country picker. CONCACAF is the abbreviation for Confederation of North, Central America and Caribbean Association Football. This covers North America, Central America, and the Caribbean. */ +"WorldCup.CountryPicker.Confederation.CONCACAF.v151" = "CONCACAF"; + +/* Section header for the Europe region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Europe.v151" = "Europa"; + +/* Section header for the North America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.NorthAmerica.v151" = "Nordamerika"; + +/* Section header for the Oceania region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.Oceania.v151" = "Oceanien"; + +/* Section header for the South America region in the World Cup country picker. */ +"WorldCup.CountryPicker.Confederation.SouthAmerica.v151" = "Sydamerika"; + +/* Country name for England in the World Cup country picker. England cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.England.v151" = "Storbritannien"; + +/* Country name for Scotland in the World Cup country picker. Scotland cannot be resolved with standard locale APIs so it needs an explicit string. */ +"WorldCup.CountryPicker.Country.Scotland.v151" = "Skottland"; + +/* Label for the done button on the country picker for the World Cup widget. This allows users to confirm their selection of a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.DoneButtonTitle.v151" = "Klar"; + +/* Title for the country picker for the World Cup widget. This is shown when the user clicks the widget on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.CountryPicker.Title.v151" = "Följ ditt team"; + +/* The title of the Group A group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupA.Title.v151" = "Grupp A"; + +/* The title of the Group B group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupB.Title.v151" = "Grupp B"; + +/* The title of the Group C group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupC.Title.v151" = "Grupp C"; + +/* The title of the Group D group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupD.Title.v151" = "Grupp D"; + +/* The title of the Group E group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupE.Title.v151" = "Grupp E"; + +/* The title of the Group F group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupF.Title.v151" = "Grupp F"; + +/* The title of the Group G group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupG.Title.v151" = "Grupp G"; + +/* The title of the Group H group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupH.Title.v151" = "Grupp H"; + +/* The title of the Group I group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupI.Title.v151" = "Grupp I"; + +/* The title of the Group J group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupJ.Title.v151" = "Grupp J"; + +/* The title of the Group K group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupK.Title.v151" = "Grupp K"; + +/* The title of the Group L group in the World Cup Group Stage. */ +"WorldCup.GroupPhase.GroupL.Title.v151" = "Grupp L"; + +/* The label for the button that allows to change the team displayed in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ChangeTeamLabel.v151" = "Ändra lag"; + +/* D is short for Days. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. T for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.DayLabel.v151" = "D"; + +/* H is short for Hours. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. St for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.HourLabel.v151" = "H"; + +/* M is short for Minutes. The layout only allows for 1–2 characters: if there is an equivalent single character for your language, use that (e.g. Mi for German). Your translation will be automatically truncated at 2 characters to avoid layout issues. */ +"WorldCup.HomepageWidget.CountDown.MinuteLabel.v151" = "M"; + +/* Title for the countdown section of the World Cup widget showing the time remaining until the World Cup event. */ +"WorldCup.HomepageWidget.CountDown.Title.v151" = "Nedräkning till VM"; + +/* The accessibility labels for the button that allow to disable World Cup notifications. */ +"WorldCup.HomepageWidget.DisableNotificationButtonAccessibilityLabel.v151" = "Inaktivera aviseringar"; + +/* The label for the button in the eliminated section that takes the user back to the team selection screen. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.ChooseTeamButtonLabel.v151" = "Välj grupp"; + +/* The description of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Description.v151" = "Välj ett annat lag för att hänga med i VM."; + +/* The accessibility label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonAccessibilityLabel.v151" = "Ta bort VM-widgeten"; + +/* The label for the button in the eliminated section that removes the World Cup widget from the homepage. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.RemoveButtonLabel.v151" = "Ta bort"; + +/* The title of the section in the World Cup widget that shows when the selected team was eliminated. */ +"WorldCup.HomepageWidget.EliminatedTeamSection.Title.v151" = "Vill du fortfarande följa med?"; + +/* A generic error message used in the World Cup widget when the match data could not be loaded. */ +"WorldCup.HomepageWidget.ErrorLabel.v151" = "Vi kunde inte ladda matchningsdata. Vänligen uppdatera."; + +/* Accessibility label for the close button on the follow your team for the World Cup widget. This allows users to hide the widget from the Firefox homepage if they do not wish to see updates about the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Close.AccessibilityLabel.v151" = "Dölj VM-uppdateringar"; + +/* Call to action for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.CTA.v151" = "Följ ditt team"; + +/* Description for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. */ +"WorldCup.HomepageWidget.FollowTeamCard.Description.v151" = "Få liveuppdateringar om matcher och mycket mer."; + +/* Title for the follow your team for the World Cup widget. This is shown on the Firefox homepage to allow users to select a team to follow for the World Cup event. 'Keep Tabs On' is an informal expression meaning to stay updated on or regularly follow something (in this case, World Cup matches and updates). It’s playfully related to Firefox in that “tabs” are a browser feature. It suggests ongoing awareness rather than active monitoring or control. Not to be translated literally as physical 'tabs' or tracking in a technical sense. the meaning is about staying informed. */ +"WorldCup.HomepageWidget.FollowTeamCard.Title.v151" = "Håll koll på världscupen"; + +/* The label indicating that the displaying match as ended. */ +"WorldCup.HomepageWidget.FTLabel.v151" = "(Heltid)"; + +/* The label for the button that allows to select a custom wallpaper in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.GetCustomWallpaperLabel.v151" = "Få anpassade bakgrundsbilder"; + +/* The label for the section in the World Cup widget showing the related matches of a team for the group phase. */ +"WorldCup.HomepageWidget.GroupPhase.RelatedMatchesLabel.v151" = "Relaterade matcher"; + +/* The label that appears for live World Cup matches. */ +"WorldCup.HomepageWidget.LiveLabel.v151" = "LIVE"; + +/* The label for the button that allows to remove the World Cup widget from the home screen in the more options panel. */ +"WorldCup.HomepageWidget.RemoveLabel.v151" = "Ta bort"; + +/* The label for the button in the World Cup widget that refreshes the currently displayed match data when an error is displayed. */ +"WorldCup.HomepageWidget.RetryButtonLabel.v151" = "Uppdatera"; + +/* The label for the 'Bronze final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.BronzeFinalLabel.v151" = "BRONS-FINAAL"; + +/* The label for the 'Final' match in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.FinalLabel.v151" = "SLUT"; + +/* The label indicating the displaying match has ended after penalties. */ +"WorldCup.HomepageWidget.RoundPhase.FullTimePenaltiesLabel.v151" = "Heltid • Straffar"; + +/* The label for the 'Quarter-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.QuarterFinalsLabel.v151" = "KVARTSFINALER"; + +/* The label for the 'Round of 16' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round16Label.v151" = "ÅTERGÅNG AV 16"; + +/* The label for the 'Round of 32' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.Round32Label.v151" = "Omgång AV 32"; + +/* The accessibility label for the scroll indicator that lets users navigate through the matches in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.ScrollIndicatorAccessibilityLabel.v151" = "Bläddra för att se föregående eller nästa matcher"; + +/* The label for the 'Semi-finals' phase in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.SemiFinalsLabel.v151" = "SEMI-FINALER"; + +/* The label for an upcoming match in the World Cup widget round phase. */ +"WorldCup.HomepageWidget.RoundPhase.UpcomingLabel.v151" = "Kommande"; + +/* The label for the World Cup championship winner in the World Cup widget. */ +"WorldCup.HomepageWidget.RoundPhase.WinWorldCupLabel.v151" = "2026 VM i fotboll"; + +/* The accessibility label for the button that allows to see more options related to World Cup widget. */ +"WorldCup.HomepageWidget.SettingsButtonAccessibilityLabel.v151" = "Fler alternativ"; + +/* The label for the button that allows to share the selected match info in the World Cup widget more options panel. */ +"WorldCup.HomepageWidget.ShareLabel.v151" = "Dela"; + +/* The description for the temporary view in the World Cup widget showing the team that has been previously selected. */ +"WorldCup.HomepageWidget.TemporaryView.Description.v151" = "Vi håller dig uppdaterad när världscupen närmar sig"; + diff --git a/firefox-ios/Shared/Supporting Files/tg.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/tg.lproj/EnhancedTrackingProtection.strings index 46b32d3c5ee70..7363a6adf04fe 100644 --- a/firefox-ios/Shared/Supporting Files/tg.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/tg.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Васоити пайгирии манъшуда: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Муҳофизати «Стандартӣ» васоити пайгирии маълумро пас аз оғози боршавии саҳифа дарҳол манъ мекунад, бинобар ин шумо метавонед шумораи зиёди васоити пайгириро мушоҳида намоед. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Муҳофизати «Ҷиддӣ» васоити пайгирии маълумро пеш аз оғози боршавии саҳифа қатъ мекунад, бинобар ин шумо метавонед шумораи камтари васоити пайгириро мушоҳида намоед. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/tg.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/tg.lproj/Settings.strings index c9e56bec49e85..ccd6a4dbec064 100644 --- a/firefox-ios/Shared/Supporting Files/tg.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/tg.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "Ҳангоми тарҷума аз забонҳои зерин истифода баред."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Нест кардан"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Забонҳои пазируфта"; diff --git a/firefox-ios/Shared/Supporting Files/tg.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/tg.lproj/Toolbar.strings index d996755a49778..5841db56864d4 100644 --- a/firefox-ios/Shared/Supporting Files/tg.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/tg.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Менюи асосӣ"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Пӯшидани варақаи ҷорӣ"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Варақаҳои кушодашуда"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/th.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/th.lproj/EnhancedTrackingProtection.strings index 5633db283c2ed..9ac10e9e62989 100644 --- a/firefox-ios/Shared/Supporting Files/th.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/th.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "ตัวติดตามที่ถูกปิดกั้น: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "แบบมาตรฐานจะปิดกั้นตัวติดตามที่พบบ่อยหลังจากที่หน้าเริ่มโหลด คุณจึงอาจเห็นจำนวนตัวติดตามที่มากขึ้น %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "แบบเข้มงวดจะปิดกั้นตัวติดตามมากขึ้นโดยหยุดตัวติดตามเหล่านั้นก่อนที่หน้าจะโหลด คุณจึงอาจเห็นจำนวนตัวติดตามที่น้อยลง %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/th.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/th.lproj/Toolbar.strings index b34b1195c7a09..e23a751210f40 100644 --- a/firefox-ios/Shared/Supporting Files/th.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/th.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "เมนูหลัก"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "ปิดแท็บนี้"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "แท็บที่เปิดอยู่"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/tr.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/tr.lproj/EnhancedTrackingProtection.strings index 4f2e43850fbf4..a86bddbfe5820 100644 --- a/firefox-ios/Shared/Supporting Files/tr.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/tr.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Engellenen takip kodları: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Standart mod, sayfalar yüklenmeye başladıktan sonra sık kullanılan takip kodlarını engeller. Bu yüzden takip kodu sayısını daha fazla görebilirsiniz. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Sıkı mod, daha fazla takip kodunu sayfa yüklenmeden önce durdurarak engeller. Bu yüzden takip kodu sayısını daha az görebilirsiniz. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/tr.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/tr.lproj/Toolbar.strings index c60abfd564b84..559e18f111325 100644 --- a/firefox-ios/Shared/Supporting Files/tr.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/tr.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Ana menü"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Bu sekmeyi kapat"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Açık sekmeler"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/NativeErrorPage.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/NativeErrorPage.strings index a0a5e8a36c973..dfdfa2aff4e76 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/NativeErrorPage.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/NativeErrorPage.strings @@ -1,3 +1,6 @@ +/* On certificate error page, this is a link to learn more about certificate errors. */ +"NativeErrorPage.BadCertDomain.LearnMoreLink.v149" = "Күбрәк белү"; + /* On error page, this is the text on a button that will try to load the page again. */ "NativeErrorPage.ButtonLabel.v131" = "Яңарту"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings index a14d314a333d6..8577e6ce84948 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Onboarding.strings @@ -76,18 +76,42 @@ /* Describes an action on some of the Onboarding screen, including the wallpaper onboarding screen. This string will be on a button so user can skip that onboarding page. */ "Onboarding.LaterAction.v115" = "Калдырып тору"; +/* Continue button on the theme selection card in the v148 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Customization.Theme.Continue.Action.v148" = "Дәвам итү"; + +/* Continue button on the toolbar customization card in the v148 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Customization.Toolbar.Continue.Action.v148" = "Дәвам итү"; + +/* Button to skip the marketing data sharing card in the v148 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Marketing.Skip.Action.v148" = "Хәзер түгел"; + +/* Button to skip the welcome card in the v148 brand refresh onboarding flow. */ +"Onboarding.Modern.BrandRefresh.Welcome.Skip.v148" = "Хәзер түгел"; + +/* String used to describe the option to save the user setting and continue to the next onboarding in Firefox Onboarding screens. */ +"Onboarding.Modern.Customization.Theme.Continue.Action.v140" = "Дәвам итү"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the bottom of the screen. */ "Onboarding.Modern.Customization.Toolbar.Bottom.Action.v140" = "Аста"; +/* Continue button on the toolbar customization card. */ +"Onboarding.Modern.Customization.Toolbar.Continue.Action.v145" = "Дәвам итү"; + /* On the toolbar customization onboarding card, the string used to describe the option to set the toolbar at the top of the screen. */ "Onboarding.Modern.Customization.Toolbar.Top.Action.v140" = "Өстә"; /* String used to describes the option to skip the Sync sign in during onboarding for the current version in Firefox Onboarding screens. */ "Onboarding.Modern.Sync.Skip.Action.v140" = "Хәзер түгел"; +/* Button to skip the Sync setup during onboarding. */ +"Onboarding.Modern.Sync.Skip.Action.v145" = "Хәзер түгел"; + /* Title for the confirmation button for Terms of Service agreement, in the Terms of Service screen. */ "Onboarding.Modern.TermsOfService.AgreementButtonTitle.v140" = "Дәвам итү"; +/* Title for the confirmation button on the Terms of Service screen. */ +"Onboarding.Modern.TermsOfService.AgreementButtonTitle.v145" = "Дәвам итү"; + /* Title for the Manage button link, in the Terms of Service screen for redirecting the user to the Manage data collection preferences screen. */ "Onboarding.Modern.TermsOfService.ManageLink.v140" = "Идарә итү"; @@ -109,6 +133,9 @@ /* Describes the action on the first onboarding page in our Onboarding screen. This string will be on a button so user can skip this onboarding card. */ "Onboarding.Modern.Welcome.Skip.v140" = "Хәзер түгел"; +/* Button to skip the welcome card. */ +"Onboarding.Modern.Welcome.Skip.v145" = "Хәзер түгел"; + /* String used to describe the option to continue to ask for the notification permission in Firefox Onboarding screens. */ "Onboarding.Notification.Continue.Action.v114" = "Дәвам итү"; diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/RelayMask.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/RelayMask.strings new file mode 100644 index 0000000000000..188a0639be1ba --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/RelayMask.strings @@ -0,0 +1,3 @@ +/* Website link title that takes users to a website to learn additional information about the Relay email mask feature. */ +"RelayMask.RelayEmailMaskSettingsLearnMore.v146" = "Күбрәк белү"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Settings.strings index fee434cfb6952..1d12eb5cf253d 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Settings.strings @@ -64,6 +64,9 @@ /* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ "Settings.AddressAutofill.Title.v126" = "Адреслар"; +/* This is the text for the link in the Header Card under the AI Controls settings section that links to more information. */ +"Settings.AIControls.HeaderCard.Link.v151" = "Күбрәк белү"; + /* Default section header title under page zoom settings. Indicates the default value for all websites */ "Settings.Appearance.Zoom.Default.Title.v140" = "Төп"; @@ -124,6 +127,9 @@ /* This is the title informing the user that they need to turn on notifications in iOS Settings. */ "Settings.Notifications.TurnOnNotificationsTitle.v112" = "Искәртүләрне кабызу"; +/* Title for a link that explains what Mozilla means by Rollouts */ +"Settings.Rollouts.Link.v148" = "Күбрәк белү"; + /* Accessibility label for default search engine setting. */ "Settings.Search.Accessibility.DefaultSearchEngine.v121" = "Стандарт эзләү системасы"; @@ -160,6 +166,12 @@ /* Title for a link that explains what Mozilla means by Studies */ "Settings.Studies.Link.v136" = "Күбрәк белү"; +/* Title for a link that explains what Mozilla means by Studies */ +"Settings.Studies.Link.v148" = "Күбрәк белү"; + /* Title for a link that explains how Mozilla send technical and interaction data. */ "Settings.TechnicalData.Link.v136" = "Күбрәк белү"; +/* Terms of Use settings section title */ +"Settings.TermsOfUse.Title.v137" = "Куллану шартлары"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Summarizer.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Summarizer.strings new file mode 100644 index 0000000000000..484dc8bd7da55 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Summarizer.strings @@ -0,0 +1,6 @@ +/* The label for the error button that allows the user to close the summary view because there is an error summarizing the page and the summary cannot be retried. */ +"Summarizer.CloseButton.Label.v142" = "Ябу"; + +/* The label for the accept button in the info panel in the summarizer when the user has not yet accepted the ToS. */ +"Summarizer.ToS.InfoPanel.ContinueButton.Label.v143" = "Дәвам итү"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/TermsOfUse.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/TermsOfUse.strings new file mode 100644 index 0000000000000..191ed6e7969b3 --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/TermsOfUse.strings @@ -0,0 +1,6 @@ +/* Label for the close button shown in the Terms of Use web view. */ +"TermsOfUse.CloseButton.v150" = "Ябу"; + +/* Title shown at the top of the Terms of Use bottom sheet for variant 1. */ +"TermsOfUse.TitleValue1.v147" = "Куллану шартлары"; + diff --git a/firefox-ios/Shared/Supporting Files/tt.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/tt.lproj/Toolbar.strings index ec62b8e9d1e1c..d2ee4b90fd5c1 100644 --- a/firefox-ios/Shared/Supporting Files/tt.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/tt.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Төп меню"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -7,7 +7,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Бу табны ябу"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Ачык таблар"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/ug.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/ug.lproj/EnhancedTrackingProtection.strings index fdf867843c013..547ecfe636809 100644 --- a/firefox-ios/Shared/Supporting Files/ug.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/ug.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "ئىزلىغۇچى توسۇلدى: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "بەت يۈكلىنىشكە باشلىغاندىن كېيىن ئۆلچەملىك ھالەت ئادەتتىكى ئىزلاشنى توسىدۇ، شۇڭا كۆپرەك ئىزلاش سانىنى كۆرۈشىڭىز مۇمكىن. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "قاتتىق توسۇش ھالىتى بەت يۈكلىنىشتىن ئىلگىرى ئۇلارنى توختىتىدۇ، شۇڭا ئازراق ئىزلاش سانىنى كۆرۈشىڭىز مۇمكىن. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/ug.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/ug.lproj/Toolbar.strings index 347b17f84b7a6..6af654644f242 100644 --- a/firefox-ios/Shared/Supporting Files/ug.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/ug.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "ئاساسىي تىزىملىك"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -10,7 +10,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "بۇ بەتكۈچنى ياپ"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "ئېچىلغان بەتكۈچ"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/Bookmarks.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/Bookmarks.strings index 0de6a4ed749b6..606292b7dc7d5 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/Bookmarks.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/Bookmarks.strings @@ -61,6 +61,9 @@ /* The label displayed in the toast notification when saving a bookmark via the menu to a custom folder. %@ represents the custom name of the folder, created by the user, where the bookmark will be saved to. */ "Bookmarks.Menu.SavedBookmarkToastLabel.v136" = "Збережено в “%@”"; +/* Placeholder text for the search field in the bookmarks panel, used to filter bookmarks by title or URL. */ +"Bookmarks.Search.Placeholder.v151" = "Шукати закладки"; + /* The title for the Edit context menu action for sites in Home Panels */ "HomePanel.ContextMenu.Edit.v131" = "Редагувати"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/EnhancedTrackingProtection.strings index 404a6ac0e2910..8823febf15ba4 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/EnhancedTrackingProtection.strings @@ -82,12 +82,21 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Заблоковано стеження: %@"; +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ +"Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Стандартний блокує поширені елементи стеження після початку завантаження сторінки, тому ви можете бачити більшу кількість на лічильнику. %@"; + +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ +"Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Надійний блокує більше елементів стеження ще до завантаження сторінки, тому ви можете бачити меншу кількість на лічильнику. %@"; + /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ "Menu.EnhancedTrackingProtection.Details.Verifier.v128" = "Засвідчено %@"; /* The title for the button that allows users to view certificates inside the enhanced tracking protection details screen. */ "Menu.EnhancedTrackingProtection.Details.ViewCertificatesTitle.v131" = "Переглянути сертифікат"; +/* Title for a link that explains how current Tracking Protection mode work. */ +"Menu.EnhancedTrackingProtection.Link.LearnMore.v150" = "Докладніше"; + /* Header for the enhanced tracking protection screen when the user has opted out of the feature. %@ is the app name (e.g. Firefox). */ "Menu.EnhancedTrackingProtection.Off.Header.v128" = "%@ не працює. Ми пропонуємо знову ввімкнути захист."; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/FirefoxHomepage.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/FirefoxHomepage.strings index 004f21abdbc69..01124e1432d69 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/FirefoxHomepage.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/FirefoxHomepage.strings @@ -16,6 +16,12 @@ /* This is the title that appears in the navigation bar for the All Stories view, a screen that displays a collection of trending news articles */ "FirefoxHome.Stories.AllStoriesViewTitle.v145" = "Усі статті"; +/* This is the label, combined with a newspaper icon and a ^ chevron, used to show affordance that scrolling the homepage up reveals the News section containing a collection of trending news articles */ +"FirefoxHome.Stories.NewsAffordanceLabel.v149" = "Новини"; + +/* This is the title of the stories section on Firefox Homepage, which displays a collection of trending news articles */ +"FirefoxHome.Stories.NewsSectionTitle.v149" = "Новини"; + /* This is the title of the stories section on Firefox Homepage, which displays a collection of trending news articles */ "FirefoxHome.Stories.PopularTodaySectionTitle.v145" = "Популярне сьогодні"; @@ -49,6 +55,9 @@ /* This is the section title for the Shortcuts section on Firefox Homepage. */ "FirefoxHomepage.Shortcuts.SectionTitle.v142" = "Ярлики"; +/* On the homepage, in the category selection bar, in the stories section, this will be the title of the button indicating that all story categories are currently selected. */ +"FirefoxHomepage.Stories.AllStoryCategories.v151" = "Усе"; + /* On the homepage, in the Stories section, this is the accessibility hint for the position of the current story in the stories stack. The first placeholder, %1$@, is the current position; the second placeholder, %2$@, is the total story count. Example: '1 of 5' */ "FirefoxHomepage.Stories.PositionAccessibilityHint.v143" = "%1$@ з %2$@"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/MainMenu.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/MainMenu.strings index b7b80f7c205a7..9ba8f4969d562 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/MainMenu.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/MainMenu.strings @@ -349,6 +349,9 @@ /* On the main menu, the accessibility label for the action that will open the page zoom tool. */ "MainMenu.ToolsSection.AccessibilityLabels.PageZoom.Title.v142" = "Масштаб сторінки"; +/* On the main menu, the accessibility label for the action that will show or hide the reader view of the webpage. */ +"MainMenu.ToolsSection.AccessibilityLabels.ReaderView.v150" = "Режим читання"; + /* On the main menu, the accessibility label for the action that will take the user to the Save submenu in the menu. In the main menu, there is an option called Save that is taking the user to the Save submenu where user can share, bookmark the page and so on. */ "MainMenu.ToolsSection.AccessibilityLabels.Save.v132" = "Зберегти"; @@ -388,6 +391,15 @@ /* On the main menu, the title for the action that will show more menu options in the current section of the menu. */ "MainMenu.ToolsSection.MoreOptions.Title.v141" = "Більше"; +/* On the main menu, the title for the action that will show the reader view for the webpage. */ +"MainMenu.ToolsSection.ReaderView.Title.v149" = "Режим читання"; + +/* On the main menu, the label for the action that indicates that Reader view is turned off. */ +"MainMenu.ToolsSection.ReaderViewOff.Title.v150" = "Вимкнено"; + +/* On the main menu, the label for the action that indicates that Reader view is turned on. */ +"MainMenu.ToolsSection.ReaderViewOn.Title.v150" = "Увімкнено"; + /* On the main menu, the title for the action that will take the user to the Save submenu in the menu. */ "MainMenu.ToolsSection.SaveSubmenu.Title.v131" = "Зберегти"; @@ -409,12 +421,21 @@ /* On the main menu, the accessibility label describing the language that the webpage was translated to. %@ is the language selected (e.g. English). */ "MainMenu.ToolsSection.Translation.AccessibilityLabels.TranslatedPageLanguage.v145" = "Перекладено: %@"; +/* On the main menu, the badge shown on the Translate Page item when translation is inactive. */ +"MainMenu.ToolsSection.Translation.Off.v151" = "Вимкнено"; + /* On the main menu, the title for the action that will translate the content of the webpage. */ "MainMenu.ToolsSection.Translation.Title.v145" = "Перекласти сторінку"; +/* On the main menu, in the multi-language translation flow, the title for the action that will open a language picker to choose a language and translate the content of the webpage. The ellipsis indicates that a language picker will open. */ +"MainMenu.ToolsSection.Translation.Title.v151" = "Перекласти сторінку…"; + /* On the main menu, the title for the action that shows that the content of the webpage is already translated. */ "MainMenu.ToolsSection.Translation.Translated.Title.v145" = "Перекладено"; +/* On the main menu, in the multi-language translation flow, the title for the action that shows the content of the webpage has been translated. Tapping opens a language picker to change the language. The ellipsis indicates that a language picker will open. */ +"MainMenu.ToolsSection.Translation.Translated.Title.v151" = "Перекладено…"; + /* On the main menu, a title for a label that indicate the Website Dark Mode option from menu, is OFF. */ "MainMenu.WebsiteDarkModeOffV2.Title.v142" = "Вимкнено"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/ReaderModeBar.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/ReaderModeBar.strings new file mode 100644 index 0000000000000..e97d42976809e --- /dev/null +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/ReaderModeBar.strings @@ -0,0 +1,3 @@ +/* Accessibility label for the summarize button in the reader mode bar view */ +"ReaderModeBar.SummarizeButtonAccessibilityLabel.v150" = "Підсумувати вміст сторінки"; + diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/Settings.strings index a8c85f9317c8e..c691b09d2d489 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/Settings.strings @@ -64,6 +64,57 @@ /* Label used as an item in Settings screen. When touched, it will take user to address autofill settings page to that will allow user to add or modify saved addresses to allow for autofill in a webpage. */ "Settings.AddressAutofill.Title.v126" = "Адреси"; +/* In the AI Controls settings, in the AI powered features section, this is the text that indicates the feature is turned on. */ +"Settings.AIControls.AIPoweredFeaturesSection.AvailableStatus.v151" = "Доступно"; + +/* In the AI Controls settings, in the AI powered features section, this is the text that what the available status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.AvailableStatusDescription.v151" = "**Доступно**: ви побачите функцію та зможете нею користуватися."; + +/* In the AI Controls settings, in the AI powered features section, this is the text that indicates the feature is turned off. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatus.v151" = "Заблоковано"; + +/* In the AI Controls settings, in the AI powered features section, this is the text that what the blocked status means. The content between the ** ** is bolded. Please do not remove these in translation. */ +"Settings.AIControls.AIPoweredFeaturesSection.BlockedStatusDescription.v151" = "**Заблоковано**: ви не бачитимете й не зможете використовувати цю функцію. Для ШІ на пристрої всі завантажені моделі вилучаються."; + +/* In the AI Controls settings, in the AI powered features section, this is the message that describes the pages summaries feature */ +"Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Message.v151" = "Сторінки та підсумки ніколи не зберігаються."; + +/* In the AI Controls settings, in the AI powered features section, this is the title that describes the page summaries feature */ +"Settings.AIControls.AIPoweredFeaturesSection.PageSummariesSection.Title.v151" = "Підсумки вмісту сторінок"; + +/* In the AI Controls settings, this is the title for the section that describes AI-powered features. This is uppercase to match the style on iOS apps. */ +"Settings.AIControls.AIPoweredFeaturesSection.Title.v151" = "ФУНКЦІЇ НА ОСНОВІ ШІ"; + +/* In the AI Controls settings, in the AI powered features section, this is the message that describes the translation feature */ +"Settings.AIControls.AIPoweredFeaturesSection.TranslationSection.Message.v151" = "Переклади залишаються конфіденційними на вашому пристрої."; + +/* In the AI Controls settings, in the AI powered features section, this is the title that describes the translation feature */ +"Settings.AIControls.AIPoweredFeaturesSection.TranslationSection.Title.v151" = "Переклад"; + +/* This is the description for the setting that toggles whether to block AI enhancements under the AI Controls settings section. %@ is the app name (e.g. Firefox). */ +"Settings.AIControls.BlockAIEnhancementsDescription.v151" = "Блокування означає, що ви не бачитимете нових або поточних покращень ШІ у %@, а також спливних повідомлень про них."; + +/* In the AI Controls settings, this is the text for the link that takes the user more information on AI features. */ +"Settings.AIControls.BlockAIEnhancementsLink.v151" = "Перегляньте відомості про функції"; + +/* This is the title for the setting that toggles whether to block AI enhancements under the AI Controls settings section. */ +"Settings.AIControls.BlockAIEnhancementsTitle.v151" = "Блокувати вдосконалення ШІ"; + +/* In the AI Controls settings, this is the text that appears when you have turned on the Block AI Enhancements toggle */ +"Settings.AIControls.BlockedInformation.v151" = "Поточні та нові вдосконалення ШІ типово заблоковано. Розблокуйте певні функції нижче."; + +/* This is the text for the link in the Header Card under the AI Controls settings section that links to more information. */ +"Settings.AIControls.HeaderCard.Link.v151" = "Докладніше"; + +/* This is the message for the Header Card under the AI Controls settings section. */ +"Settings.AIControls.HeaderCard.Message.v151" = "Це передбачає можливість використання функцій, вдосконалених ШІ."; + +/* This is the title for the Header Card under the AI Controls settings section. %@ is the app name (e.g. Firefox). */ +"Settings.AIControls.HeaderCard.Title.v151" = "З %@ у вас завжди є вибір"; + +/* In the settings menu, in the General section, this is the title for the AI Controls settings section */ +"Settings.AIControls.Title.v150" = "Керування ШІ"; + /* Section description in Appearance settings for middle button configuration of the bottom toolbar. */ "Settings.Appearance.NavigationToolbar.Description.v145" = "Зміна кнопки в центрі панелі інструментів."; @@ -271,6 +322,18 @@ /* This is the section title for the gestures features under the Summarize settings section. */ "Settings.Summarize.GesturesSection.Title.v142" = "Жести"; +/* The accessibility label for the language picker button in the Summarize settings. */ +"Settings.Summarize.LanguageSection.PickerButtonAccessibilityLabel.v149" = "Вибрати мову"; + +/* The label for the picker option to select the preferred app language for the summarizer inside the Summarize settings. The preferred app language refers to the language that the user has selected to use for the app in the app settings. */ +"Settings.Summarize.LanguageSection.PreferredAppLanguageLabel.v149" = "Бажана мова програми"; + +/* The title for the language choose section for the summarizer inside the Summarize settings. */ +"Settings.Summarize.LanguageSection.Title.v149" = "Мова"; + +/* The label for the picker option to select the website language for the summarizer inside the Summarize settings. */ +"Settings.Summarize.LanguageSection.WebsiteLanguageLabel.v149" = "Мова вебсайту"; + /* This is the title for the setting that toggles the Summarize feature under the Summarize settings section. */ "Settings.Summarize.SummarizePagesTitle.v142" = "Підсумок вмісту сторінок"; @@ -295,6 +358,33 @@ /* In the Appearance settings menu, in the Toolbar Button section, this label indicates that selecting this will make the New Tab button appear in the middle of the navigation toolbar. */ "Settings.Toolbar.Navigation.MiddleButton.NewTab.v145" = "Нова вкладка"; +/* Footer text below the auto-translate toggle in the Translation settings screen. */ +"Settings.Translation.AutoTranslate.Footer.v151" = "Автоматичний переклад сторінок вашою бажаною мовою."; + +/* Title for the auto-translate toggle in the Translation settings screen. */ +"Settings.Translation.AutoTranslate.Title.v151" = "Автоматичний переклад"; + +/* Navigation bar title of the language picker shown when adding a preferred translation language. */ +"Settings.Translation.LanguagePicker.NavTitle.v151" = "Вибрати мову"; + +/* Placeholder text for the search bar in the translation language picker. */ +"Settings.Translation.LanguagePicker.SearchPlaceholder.v151" = "Пошук"; + +/* Row label in the preferred languages list that opens the language picker to add a new preferred language for translation. */ +"Settings.Translation.PreferredLanguages.AddLanguage.v151" = "Додати мову…"; + +/* Subtitle on the device language row in the preferred languages list in the Translation settings screen. */ +"Settings.Translation.PreferredLanguages.DeviceLanguage.v151" = "Мова пристрою"; + +/* Footer text below the preferred languages list in the Translation settings screen. */ +"Settings.Translation.PreferredLanguages.Footer.v151" = "Під час перекладу вибирайте серед цих мов."; + +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Видалити"; + +/* Section header for the preferred languages list in the Translation settings screen. */ +"Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Бажані мови"; + /* This is the section title text for the the Translation feature under the Translation settings section. */ "Settings.Translation.SectionTitle.v145" = "Налаштування перекладу"; @@ -307,6 +397,9 @@ /* In the settings menu, in the General section, this is the title for the Translation settings section. */ "Settings.Translation.Title.v145" = "Переклад"; +/* Footer text below the enable toggle in the Translation settings screen. */ +"Settings.Translation.ToggleFooter.v151" = "Вимкніть цю функцію, щоб вилучити переклад з панелі інструментів і меню."; + /* This is the title for the setting that toggles the Translation feature that allows users to translate web pages under the Translation settings section. */ "Settings.Translation.ToggleTitle.v145" = "Увімкнути переклад"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/Summarizer.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/Summarizer.strings index 0cf531251be52..faac12a4f7d89 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/Summarizer.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/Summarizer.strings @@ -43,6 +43,9 @@ /* The a11y label for the web page view that shows on top of the summary view. Tapping or dragging on the view will close the summary page. */ "Summarizer.TabSnapshot.Accessibility.Label.v145" = "Перетягніть або торкніться сторінки, щоб закрити підсумок"; +/* The label for the toast that shows when shaking and the summary is not available for that page. */ +"Summarizer.ToastLabel.v149" = "Підсумок недоступний"; + /* The label for the learn more link button on the Terms of Service alert for the summarizer. */ "Summarizer.ToS.InfoLabel.LearnMoreButton.Label.v143" = "Як це працює"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/TabsTray.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/TabsTray.strings index 0c5255083e9c9..3ebe3eefc6567 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/TabsTray.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/TabsTray.strings @@ -16,6 +16,9 @@ /* Button label to sync tabs in your account */ "TabsTray.SyncTabs.SyncTabsButton.Title.v119" = "Синхронізувати вкладки"; +/* When user choose to have Blank Page as homepage, this will be displayed as tab title. */ +"TabTray.TabsSelectorBlankTabsTitle.v149" = "Нова вкладка"; + /* The title on the button to look at regular tabs. */ "TabTray.TabsSelectorNormalTabsTitle.v140" = "Вкладки"; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/TermsOfUse.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/TermsOfUse.strings index e4d265da8056d..f3612d6f0557f 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/TermsOfUse.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/TermsOfUse.strings @@ -4,6 +4,9 @@ /* Label for the back button shown in the Terms of Use web view. */ "TermsOfUse.BackButton.v142" = "Назад"; +/* Label for the close button shown in the Terms of Use web view. */ +"TermsOfUse.CloseButton.v150" = "Закрити"; + /* Introductory message in the Terms of Use sheet that mentions updated Terms of Use and Privacy Notice. %@ will be replaced with the app name (e.g., Firefox). */ "TermsOfUse.Description.v142" = "Ми запровадили Умови користування %@ та оновили нашу Політику приватності."; diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/Toolbar.strings index fbeaa63e9dbae..1b88933e4ff68 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Головне меню"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -7,10 +7,13 @@ /* Accessibility label for the summarize button that can be displayed in the address toolbar. */ "Toolbar.NewTab.Button.v142" = "Підсумувати вміст сторінки"; +/* Accessibility label for the reader view button with a bottom badge that indicates that the summary is available for the page. The button is displayed in the address bar. */ +"Toolbar.ReaderModeWithSummarizer.Button.v150" = "Режим читання. Доступний підсумок вмісту сторінки."; + /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Закрити цю вкладку"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Відкриті вкладки"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/uk.lproj/Translations.strings b/firefox-ios/Shared/Supporting Files/uk.lproj/Translations.strings index 8398a60a8a5b0..8227570d5de29 100644 --- a/firefox-ios/Shared/Supporting Files/uk.lproj/Translations.strings +++ b/firefox-ios/Shared/Supporting Files/uk.lproj/Translations.strings @@ -1,3 +1,9 @@ +/* Button label on the auto-translate prompt that enables the auto-translate feature when tapped. */ +"Translations.AutoTranslatePrompt.EnableButton.v151" = "Увімкнути"; + +/* Persistent prompt shown above the address bar after the user's first manual translation, asking if they want to enable auto-translate. */ +"Translations.AutoTranslatePrompt.Message.v151" = "Автоматично перекладати сторінки, коли це можливо?"; + /* On top of the bottom toolbar, there can be a translations banner, this is the accessibility label for the close button that appears on the banner that allows the user to dismiss the translations banner. */ "Translations.Banner.Close.Button.AccessibilityLabel.v145" = "Закрити панель перекладу"; @@ -22,6 +28,18 @@ /* On top of the bottom toolbar, there can be a translations banner, this is the title for the button that appears on the banner that allows the user to tap on and translate the webpage. */ "Translations.Banner.TranslateButton.v145" = "Перекласти"; +/* Title for the action sheet shown when the page has already been translated. %@ is replaced with the target language name (e.g. 'English'). */ +"Translations.LanguagePicker.PageTranslatedTitle.v151" = "Сторінку перекладено: %@"; + +/* Menu item at the bottom of the translate language picker that navigates the user to the Translation Preferred Languages settings screen. */ +"Translations.LanguagePicker.PreferredLanguages.v151" = "Бажані мови…"; + +/* Button in the translation action sheet to restore the page to its original language. */ +"Translations.LanguagePicker.ShowOriginal.v151" = "Показати оригінал"; + +/* Title for the action sheet that appears when the user taps the translate toolbar button. Lists the preferred target languages the user can translate the page into. */ +"Translations.LanguagePicker.Title.v151" = "Перекласти сторінку…"; + /* This is the text on the button used to translate a page from one language to another and the button is shown on the translation feature bottom sheet view. */ "Translations.Sheet.Button.v145" = "Перекласти"; diff --git a/firefox-ios/Shared/Supporting Files/vi.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/vi.lproj/EnhancedTrackingProtection.strings index 56efcaed29c8e..558b57932e7f1 100644 --- a/firefox-ios/Shared/Supporting Files/vi.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/vi.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "Trình theo dõi bị chặn: %@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "Trình chặn tiêu chuẩn thường được sử dụng sẽ chặn các trình theo dõi phổ biến sau khi trang bắt đầu tải, vì vậy bạn có thể thấy số lượng trình theo dõi cao hơn. %@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "Chế độ nghiêm ngặt chặn nhiều trình theo dõi hơn bằng cách ngăn chúng trước khi trang tải xong, vì vậy bạn có thể thấy số lượng trình theo dõi thấp hơn. %@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/vi.lproj/Settings.strings b/firefox-ios/Shared/Supporting Files/vi.lproj/Settings.strings index a7a409f10caf2..26207ce4bffb0 100644 --- a/firefox-ios/Shared/Supporting Files/vi.lproj/Settings.strings +++ b/firefox-ios/Shared/Supporting Files/vi.lproj/Settings.strings @@ -379,6 +379,9 @@ /* Footer text below the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.Footer.v151" = "Hãy chọn một trong những ngôn ngữ này khi dịch."; +/* VoiceOver custom action label on a preferred language row in the Translation settings screen, used to remove that language from the list. */ +"Settings.Translation.PreferredLanguages.RemoveLanguageA11yAction.v151" = "Xóa"; + /* Section header for the preferred languages list in the Translation settings screen. */ "Settings.Translation.PreferredLanguages.SectionTitle.v151" = "Ngôn ngữ ưu tiên"; diff --git a/firefox-ios/Shared/Supporting Files/vi.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/vi.lproj/Toolbar.strings index 2f6a7d59b4666..f2fc020644712 100644 --- a/firefox-ios/Shared/Supporting Files/vi.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/vi.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "Menu chính"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "Đóng thẻ này"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "Thẻ đang mở"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/zh-CN.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/zh-CN.lproj/EnhancedTrackingProtection.strings index 45f166404be2f..bc22d26b33817 100644 --- a/firefox-ios/Shared/Supporting Files/zh-CN.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/zh-CN.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "已拦截跟踪器:%@ 个"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "标准模式下,将在页面开始加载后拦截常见跟踪器,因此显示的跟踪器数量可能较多。%@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "严格模式下,拦截将在页面加载前进行,从而阻止更多跟踪器,因此显示的跟踪器数量可能较少。%@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/zh-CN.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/zh-CN.lproj/Toolbar.strings index 3f470c1fb1dbe..64594e9e6369a 100644 --- a/firefox-ios/Shared/Supporting Files/zh-CN.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/zh-CN.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "主菜单"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "关闭此标签页"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "打开的标签页"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/Supporting Files/zh-TW.lproj/EnhancedTrackingProtection.strings b/firefox-ios/Shared/Supporting Files/zh-TW.lproj/EnhancedTrackingProtection.strings index 58f512befdc15..bf8e363d615b9 100644 --- a/firefox-ios/Shared/Supporting Files/zh-TW.lproj/EnhancedTrackingProtection.strings +++ b/firefox-ios/Shared/Supporting Files/zh-TW.lproj/EnhancedTrackingProtection.strings @@ -82,10 +82,10 @@ /* Text to let users know how many trackers were blocked on the current website. %@ is the number of trackers blocked. */ "Menu.EnhancedTrackingProtection.Details.Trackers.v128" = "追蹤器封鎖數量:%@"; -/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how standard mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStandardModeFooterText.v150" = "標準模式會在頁面開始載入後,才自動封鎖常見的追蹤器,所以您看到的追蹤器數量可能較多。%@"; -/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more informations about current Tracking Protection mode. e.g Learn more */ +/* Text to let users know how strict mode for Tracking Protection work. %@ is a tappable text which contains a link with more information about current Tracking Protection mode. e.g Learn more */ "Menu.EnhancedTrackingProtection.Details.TrackersStrictModeFooterText.v150" = "嚴格模式會在頁面開始載入前就自動封鎖,所以您看到的追蹤器數量可能較少。%@"; /* Text to let users know the site verifier, where %@ represents the SSL certificate signer which is on the enhanced tracking protection screen after the user taps on the connection details. */ diff --git a/firefox-ios/Shared/Supporting Files/zh-TW.lproj/Toolbar.strings b/firefox-ios/Shared/Supporting Files/zh-TW.lproj/Toolbar.strings index 959498e689833..9ed46336fc21d 100644 --- a/firefox-ios/Shared/Supporting Files/zh-TW.lproj/Toolbar.strings +++ b/firefox-ios/Shared/Supporting Files/zh-TW.lproj/Toolbar.strings @@ -1,4 +1,4 @@ -/* Accessibility label for the Main Menu button in the toolbar, specifing that the button will open Main Menu */ +/* Accessibility label for the Main Menu button in the toolbar, specifying that the button will open Main Menu */ "Toolbar.Menu.Button.A11y.Label.v135" = "主選單"; /* Accessibility label for the new tab button that can be displayed in the navigation or address toolbar. */ @@ -13,7 +13,7 @@ /* Label for button on action sheet, accessed via long pressing tab toolbar button, that closes the current tab when pressed */ "Toolbar.Tab.CloseThisTab.Button.v130" = "關閉此分頁"; -/* Accessibility label for the tabs button in the toolbar, specifing the number of tabs open. */ +/* Accessibility label for the tabs button in the toolbar, specifying the number of tabs open. */ "Toolbar.Tabs.Button.A11y.Label.v135" = "開啟的分頁數量"; /* Large content title for the tabs button in the toolbar, specifying the number of tabs open. %@ is the number of open tabs. */ diff --git a/firefox-ios/Shared/sl.lproj/Localizable.strings b/firefox-ios/Shared/sl.lproj/Localizable.strings index af97f0b8458f2..a6b6dc7633cf5 100644 --- a/firefox-ios/Shared/sl.lproj/Localizable.strings +++ b/firefox-ios/Shared/sl.lproj/Localizable.strings @@ -104,7 +104,7 @@ "Bookmarks.Folder.Label" = "Mapa"; /* A label indicating all bookmarks grouped under the category 'Desktop Bookmarks'. */ -"Bookmarks.Menu.DesktopBookmarks" = "Namizni zaznamki"; +"Bookmarks.Menu.DesktopBookmarks" = "Zaznamki na namizju"; /* The button to create a new bookmark */ "Bookmarks.NewBookmark.Label" = "Nov zaznamek"; @@ -1394,13 +1394,13 @@ "Settings.NewTab.CustomURL" = "URL po meri"; /* Option in settings to show a blank page when you open a new tab */ -"Settings.NewTab.Option.BlankPage" = "Prazno stran"; +"Settings.NewTab.Option.BlankPage" = "Prazna stran"; /* Option in settings to show your homepage when you open a new tab */ "Settings.NewTab.Option.Custom" = "Po meri"; /* Option in settings to show Firefox Home when you open a new tab */ -"Settings.NewTab.Option.FirefoxHome" = "Domačo stran Firefoxa"; +"Settings.NewTab.Option.FirefoxHome" = "Domača stran Firefoxa"; /* Option in settings to show your homepage when you open a new tab */ "Settings.NewTab.Option.HomePage" = "Domača stran"; diff --git a/firefox-ios/Storage/Rust/RustRemoteTabs.swift b/firefox-ios/Storage/Rust/RustRemoteTabs.swift index 892cd1c946fd1..6dbcf55cb90dd 100644 --- a/firefox-ios/Storage/Rust/RustRemoteTabs.swift +++ b/firefox-ios/Storage/Rust/RustRemoteTabs.swift @@ -202,21 +202,6 @@ public class RustRemoteTabs: @unchecked Sendable { } } - public func removeRemoteCommand(deviceId: String, url: URL) { - queue.async { [unowned self] in - guard let tabsCommandQueue = self.tabsCommandQueue else { - let err = TabsApiError.UnexpectedTabsError(reason: "Command queue is not initialized") as MaybeErrorType - self.logger.log(err.description, - level: .warning, - category: .tabs) - return - } - - tabsCommandQueue - .removeRemoteCommand(deviceId: deviceId, command: RemoteCommand.closeTab(url: url.absoluteString)) - } - } - public func getUnsentCommandUrlsByDeviceId(deviceId: String, completion: @Sendable @escaping ([String]) -> Void) { self.getUnsentCommandsByDeviceId(deviceId: deviceId) { commands in let urls = commands.map { item in diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Autofill/FormAutofillHelperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Autofill/FormAutofillHelperTests.swift index 1250c40af1aad..4eecbee5d8e69 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Autofill/FormAutofillHelperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Autofill/FormAutofillHelperTests.swift @@ -217,7 +217,7 @@ final class FormAutofillHelperTests: XCTestCase { } @MainActor - func test_formAutofillHelper_foundFieldValuesClosure_doesntLeak() { + func test_formAutofillHelper_foundFieldValuesClosure_doesntLeak() async { let tab = Tab(profile: profile, windowUUID: windowUUID) let subject = FormAutofillHelper(tab: tab) trackForMemoryLeaks(subject) @@ -229,7 +229,7 @@ final class FormAutofillHelperTests: XCTestCase { tabWebView.accessoryView.savedCardsClosure = {} } - tab.close() + await tab.close() } @MainActor diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerStateTests.swift index d773a5d72a090..315056810b203 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerStateTests.swift @@ -482,6 +482,34 @@ final class BrowserViewControllerStateTests: XCTestCase, StoreTestUtility { } } + // MARK: - Quick Answers + func test_tapOnQuickAnswersButton_navigationBrowserAction_returnsExpectedState() throws { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + let action = getNavigationBrowserAction(for: .tapOnQuickAnswersButton, destination: .quickAnswers) + let newState = reducer(initialState, action) + + XCTAssertEqual(newState.navigationDestination?.destination, .quickAnswers) + } + + func test_navigationDestinationHandled_clearsNavigationDestination() { + let initialState = createSubject() + let reducer = browserViewControllerReducer() + + let navigateAction = getNavigationBrowserAction(for: .tapOnQuickAnswersButton, destination: .quickAnswers) + let navigatedState = reducer(initialState, navigateAction) + + let handledAction = getNavigationBrowserAction( + for: .navigationDestinationHandled, + destination: .quickAnswers + ) + let handledState = reducer(navigatedState, handledAction) + + XCTAssertNotNil(navigatedState.navigationDestination) + XCTAssertNil(handledState.navigationDestination) + } + // MARK: - Private private func createSubject() -> BrowserViewControllerState { return BrowserViewControllerState(windowUUID: .XCTestDefaultUUID) @@ -492,7 +520,7 @@ final class BrowserViewControllerStateTests: XCTestCase, StoreTestUtility { } private func getAction(for actionType: GeneralBrowserActionType) -> GeneralBrowserAction { - return GeneralBrowserAction(windowUUID: .XCTestDefaultUUID, actionType: actionType) + return GeneralBrowserAction(windowUUID: .XCTestDefaultUUID, actionType: actionType) } private func getNavigationBrowserAction( diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerTests.swift index 75b0dffeead21..523468474669d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/BrowserViewController/BrowserViewControllerTests.swift @@ -24,27 +24,26 @@ class BrowserViewControllerTests: XCTestCase, StoreTestUtility { override func setUp() async throws { try await super.setUp() - setIsSwipingTabsEnabled(false) - setIsHostedSummarizerEnabled(false) tabManager = MockTabManager() - DependencyHelperMock().bootstrapDependencies(injectedTabManager: tabManager) - profile = MockProfile() browserCoordinator = MockBrowserCoordinator() appStartupTelemetry = MockAppStartupTelemetry() recordVisitManager = MockRecordVisitObservationManager() LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + DependencyHelperMock().bootstrapDependencies(injectedTabManager: tabManager) + setIsSwipingTabsEnabled(false) + setIsHostedSummarizerEnabled(false) setupStore() } override func tearDown() async throws { + DependencyHelperMock().reset() profile.shutdown() profile = nil tabManager = nil appStartupTelemetry = nil recordVisitManager = nil resetStore() - DependencyHelperMock().reset() try await super.tearDown() } @@ -136,7 +135,7 @@ class BrowserViewControllerTests: XCTestCase, StoreTestUtility { subject.tabManager(tabManager, didSelectedTabChange: testTab, previousTab: testTab, isRestoring: false) wait(for: [expectation]) - let actionCalled = try XCTUnwrap(mockStore.dispatchedActions[2] as? GeneralBrowserAction) + let actionCalled = try XCTUnwrap(mockStore.dispatchedActions[0] as? GeneralBrowserAction) let actionType = try XCTUnwrap(actionCalled.actionType as? GeneralBrowserActionType) XCTAssertEqual(mockStore.dispatchedActions.count, 5) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift index 5790601cade87..b9bb179795cb1 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/BrowserCoordinatorTests.swift @@ -15,7 +15,9 @@ import Shared @testable import Client @MainActor -final class BrowserCoordinatorTests: XCTestCase, LegacyFeatureFlaggable, StoreTestUtility { +final class BrowserCoordinatorTests: XCTestCase, + FeatureFlaggable, + StoreTestUtility { private var mockRouter: MockRouter! private var profile: MockProfile! private var overlayModeManager: MockOverlayModeManager! @@ -78,7 +80,7 @@ final class BrowserCoordinatorTests: XCTestCase, LegacyFeatureFlaggable, StoreTe let subject = createSubject() subject.start(with: nil) // TODO: FXIOS-12947 - Add tests for ToU Feature implementation - if !featureFlags.isFeatureEnabled(.touFeature, checking: .buildOnly) { + if !featureFlagsProvider.isEnabled(.touFeature) { XCTAssertNotNil(mockRouter.rootViewController as? BrowserViewController) XCTAssertEqual(mockRouter.setRootViewControllerCalled, 1) XCTAssertTrue(subject.childCoordinators.isEmpty) @@ -258,7 +260,7 @@ final class BrowserCoordinatorTests: XCTestCase, LegacyFeatureFlaggable, StoreTe XCTAssertNotNil(subject.childCoordinators[0] as? EnhancedTrackingProtectionCoordinator) XCTAssertEqual(mockRouter.presentCalled, 1) - if featureFlags.isFeatureEnabled(.trackingProtectionRefactor, checking: .buildOnly) { + if featureFlagsProvider.isEnabled(.trackingProtectionRefactor) { XCTAssertTrue(mockRouter.presentedViewController is UINavigationController) } else { XCTAssertTrue(mockRouter.presentedViewController is EnhancedTrackingProtectionMenuVC) @@ -603,6 +605,38 @@ final class BrowserCoordinatorTests: XCTestCase, LegacyFeatureFlaggable, StoreTe await Task.yield() } + // MARK: - Quick Answers + + func testShowQuickAnswers_addsQuickAnswersCoordinator() { + let subject = createSubject() + + subject.showQuickAnswers() + + XCTAssertEqual(subject.childCoordinators.count, 1) + XCTAssertTrue(subject.childCoordinators.first is QuickAnswersCoordinator) + XCTAssertEqual(mockRouter.presentCalled, 1) + } + + func testShowQuickAnswers_doesNotAddDuplicateCoordinator() { + let subject = createSubject() + + subject.showQuickAnswers() + subject.showQuickAnswers() + + let count = subject.childCoordinators.count { $0 is QuickAnswersCoordinator } + XCTAssertEqual(count, 1) + } + + func testShowQuickAnswers_didFinish_removesChild() throws { + let subject = createSubject() + subject.showQuickAnswers() + + let coordinator = try XCTUnwrap(subject.childCoordinators.first as? QuickAnswersCoordinator) + subject.didFinish(from: coordinator) + + XCTAssertTrue(subject.childCoordinators.isEmpty) + } + // MARK: - Shortcuts Library func testShowShortcutsLibrary_showsShortcutsLibrary() throws { @@ -680,24 +714,43 @@ final class BrowserCoordinatorTests: XCTestCase, LegacyFeatureFlaggable, StoreTe func testDidRemoveTab_removesHomepageTabStateForTab() throws { let subject = createSubject() let tab = MockTab(profile: profile, windowUUID: windowUUID) - homepageTabStateStore.updateState(for: tab.tabUUID) { $0.scrollOffsetY = 180 } + homepageTabStateStore.updateState(for: tab.tabUUID) { state in + state.scrollOffsetY = 180 + state.selectedNewsfeedCategoryID = "technology" + state.newsfeedCategoryPickerOffsetX = 64 + } subject.tabManager(tabManager, didRemoveTab: tab, isRestoring: false) - XCTAssertNil(homepageTabStateStore.state(for: tab.tabUUID).scrollOffsetY) + XCTAssertEqual(homepageTabStateStore.state(for: tab.tabUUID), HomepageTabState()) } func testDidRemoveTab_keepsHomepageTabStateForOtherTabs() throws { let subject = createSubject() let removedTab = MockTab(profile: profile, windowUUID: windowUUID) let otherTab = MockTab(profile: profile, windowUUID: windowUUID) - homepageTabStateStore.updateState(for: removedTab.tabUUID) { $0.scrollOffsetY = 120 } - homepageTabStateStore.updateState(for: otherTab.tabUUID) { $0.scrollOffsetY = 240 } + homepageTabStateStore.updateState(for: removedTab.tabUUID) { state in + state.scrollOffsetY = 120 + state.selectedNewsfeedCategoryID = "science" + state.newsfeedCategoryPickerOffsetX = 40 + } + homepageTabStateStore.updateState(for: otherTab.tabUUID) { state in + state.scrollOffsetY = 240 + state.selectedNewsfeedCategoryID = "technology" + state.newsfeedCategoryPickerOffsetX = 72 + } subject.tabManager(tabManager, didRemoveTab: removedTab, isRestoring: false) - XCTAssertNil(homepageTabStateStore.state(for: removedTab.tabUUID).scrollOffsetY) - XCTAssertEqual(homepageTabStateStore.state(for: otherTab.tabUUID).scrollOffsetY, 240) + XCTAssertEqual(homepageTabStateStore.state(for: removedTab.tabUUID), HomepageTabState()) + XCTAssertEqual( + homepageTabStateStore.state(for: otherTab.tabUUID), + HomepageTabState( + scrollOffsetY: 240, + selectedNewsfeedCategoryID: "technology", + newsfeedCategoryPickerOffsetX: 72 + ) + ) } // MARK: - Search route diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift index 174556117f09e..984351298df30 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/Mocks/MockBrowserCoordinator.swift @@ -52,6 +52,7 @@ class MockBrowserCoordinator: BrowserNavigationHandler, var shouldShowNewTabToastCalled = 0 var popToBVCCalled = 0 var openLearnMoreFromNativeErrorPageCalled = 0 + var showQuickAnswersCalled = 0 func show(settings: Client.Route.SettingsSection, onDismiss: (() -> Void)?) { showSettingsCalled += 1 @@ -175,6 +176,10 @@ class MockBrowserCoordinator: BrowserNavigationHandler, openLearnMoreFromNativeErrorPageCalled += 1 } + func showQuickAnswers() { + showQuickAnswersCalled += 1 + } + // MARK: - BrowserDelegate func show(webView: WKWebView) { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/VoiceSearchCoordinatorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/QuickAnswersCoordinatorTests.swift similarity index 100% rename from firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/VoiceSearchCoordinatorTests.swift rename to firefox-ios/firefox-ios-tests/Tests/ClientTests/Coordinators/QuickAnswersCoordinatorTests.swift diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DefaultThemeManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DefaultThemeManagerTests.swift index bc2c2799d553a..d567b8166a857 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DefaultThemeManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DefaultThemeManagerTests.swift @@ -17,10 +17,12 @@ final class DefaultThemeManagerTests: XCTestCase { override func setUp() async throws { try await super.setUp() userDefaults = MockUserDefaults() + DependencyHelperMock().bootstrapDependencies() } override func tearDown() async throws { userDefaults = nil + DependencyHelperMock().reset() try await super.tearDown() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/FeatureFlagManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/FeatureFlagManagerTests.swift index 5d084ee111789..b3a95f7cd1748 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/FeatureFlagManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/FeatureFlagManagerTests.swift @@ -32,34 +32,7 @@ final class FeatureFlagManagerTests: XCTestCase, LegacyFeatureFlaggable { // Tests for default settings should be performed on both build and user // prefs separately to ensure that we are getting the expected results on both. // Technically, at this stage, these should be the same. - XCTAssertTrue(featureFlags.isFeatureEnabled(.bottomSearchBar, checking: .buildOnly)) - XCTAssertTrue(featureFlags.isFeatureEnabled(.bottomSearchBar, checking: .userOnly)) XCTAssertTrue(featureFlags.isFeatureEnabled(.reportSiteIssue, checking: .buildOnly)) XCTAssertTrue(featureFlags.isFeatureEnabled(.reportSiteIssue, checking: .userOnly)) } - - func testDefaultNimbusCustomFlags() { - XCTAssertEqual(featureFlags.getCustomState(for: .searchBarPosition), SearchBarPosition.top) - } - - // Changing the prefs manually, to make sure settings are respected through - // the FFMs interface - func testManagerRespectsProfileChangesForCustomSettings() { - let mockProfile = MockProfile(databasePrefix: "FeatureFlagsManagerTests_") - mockProfile.prefs.clearAll() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) - - // Search Bar position - XCTAssertEqual(featureFlags.getCustomState(for: .searchBarPosition), SearchBarPosition.top) - mockProfile.prefs.setString(SearchBarPosition.bottom.rawValue, - forKey: PrefsKeys.FeatureFlags.SearchBarPosition) - XCTAssertEqual(featureFlags.getCustomState(for: .searchBarPosition), SearchBarPosition.bottom) - } - - func testManagerInterfaceForUpdatingCustomFlags() { - // Search Bar - XCTAssertEqual(featureFlags.getCustomState(for: .searchBarPosition), SearchBarPosition.top) - featureFlags.set(feature: .searchBarPosition, to: SearchBarPosition.bottom) - XCTAssertEqual(featureFlags.getCustomState(for: .searchBarPosition), SearchBarPosition.bottom) - } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/RemoteTabsPanelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/RemoteTabsPanelTests.swift index d524d7c9b54f6..f82b25a2e154b 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/RemoteTabsPanelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/RemoteTabsPanelTests.swift @@ -109,19 +109,6 @@ final class RemoteTabsPanelTests: XCTestCase, StoreTestUtility { XCTAssertEqual(actionType, RemoteTabsPanelActionType.closeSelectedRemoteURL) } - func testRemoteTabsClientAndTabsDataSourceDidUndo_dispatchesUndoCloseSelectedRemoteURL() throws { - let subject = createSubject() - subject.remoteTabsClientAndTabsDataSourceDidUndo( - deviceId: Constants.testDeviceId, - url: URL(string: Constants.testUrlString)! - ) - - let action = try XCTUnwrap(mockStore.dispatchedActions.first) - let actionType = try XCTUnwrap(action.actionType as? RemoteTabsPanelActionType) - - XCTAssertEqual(actionType, RemoteTabsPanelActionType.undoCloseSelectedRemoteURL) - } - func testRemoteTabsClientAndTabsDataSourceDidTabCommandsFlush_dispatchesFlushTabCommands() throws { let subject = createSubject() subject.remoteTabsClientAndTabsDataSourceDidTabCommandsFlush(deviceId: Constants.testDeviceId) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/TopSitesHelperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/TopSitesHelperTests.swift index 750ee7a063d7f..efae7c0e7de18 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/TopSitesHelperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/TopSitesHelperTests.swift @@ -20,15 +20,17 @@ class TopSitesHelperTests: XCTestCase { } catch {} } - override func tearDown() { + override func tearDown() async throws { self.deleteDatabases() + DependencyHelperMock().reset() self.profile = nil - super.tearDown() + try await super.tearDown() } - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() self.profile = MockProfile(databasePrefix: "TopSitesHelperTests") + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) // Just in case tearDown didn't run or succeed last time! self.deleteDatabases() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/HomepageDiffableDataSourceTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/HomepageDiffableDataSourceTests.swift index c35eb237c46f8..88a5255e6ce63 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/HomepageDiffableDataSourceTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/HomepageDiffableDataSourceTests.swift @@ -84,11 +84,11 @@ final class HomepageDiffableDataSourceTests: XCTestCase { ) let snapshot = dataSource.snapshot() - XCTAssertEqual(snapshot.numberOfItems(inSection: .pocket(.systemCyan, nil)), 20) + XCTAssertEqual(snapshot.numberOfItems(inSection: .pocket(.systemCyan)), 20) let expectedSections: [HomepageSection] = [ .header, .spacer, - .pocket(.systemCyan, nil) + .pocket(.systemCyan) ] XCTAssertEqual(snapshot.sectionIdentifiers, expectedSections) } @@ -182,11 +182,11 @@ final class HomepageDiffableDataSourceTests: XCTestCase { dataSource.updateSnapshot(state: state, jumpBackInDisplayConfig: mockSectionConfig) let snapshot = dataSource.snapshot() - XCTAssertEqual(snapshot.numberOfItems(inSection: .pocket(nil, nil)), 20) + XCTAssertEqual(snapshot.numberOfItems(inSection: .pocket(nil)), 20) let expectedSections: [HomepageSection] = [ .header, .spacer, - .pocket(nil, nil) + .pocket(nil) ] XCTAssertEqual(snapshot.sectionIdentifiers, expectedSections) } @@ -207,7 +207,7 @@ final class HomepageDiffableDataSourceTests: XCTestCase { dataSource.updateSnapshot(state: state, jumpBackInDisplayConfig: mockSectionConfig) let snapshot = dataSource.snapshot() - let items = snapshot.itemIdentifiers(inSection: .pocket(nil, nil)) + let items = snapshot.itemIdentifiers(inSection: .pocket(nil)) XCTAssertEqual(items.count, 3) XCTAssertEqual(merinoTitles(from: items), ["science 1", "science 2", "technology 1"]) @@ -233,7 +233,7 @@ final class HomepageDiffableDataSourceTests: XCTestCase { ) let snapshot = dataSource.snapshot() - let items = snapshot.itemIdentifiers(inSection: .pocket(nil, "technology")) + let items = snapshot.itemIdentifiers(inSection: .pocket(nil)) XCTAssertEqual(items.count, 1) XCTAssertEqual(merinoTitles(from: items), ["technology 1"]) @@ -260,7 +260,7 @@ final class HomepageDiffableDataSourceTests: XCTestCase { let snapshot = dataSource.snapshot() - XCTAssertFalse(snapshot.sectionIdentifiers.contains(.pocket(nil, "missing-category"))) + XCTAssertFalse(snapshot.sectionIdentifiers.contains(.pocket(nil))) } @MainActor diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/NewsTransitionHeaderCellTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/NewsTransitionHeaderCellTests.swift index b1e96e5615288..a9cf257da69d9 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/NewsTransitionHeaderCellTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/NewsTransitionHeaderCellTests.swift @@ -67,6 +67,53 @@ final class NewsTransitionHeaderCellTests: XCTestCase { XCTAssertEqual(sectionTitleStackView(in: view)?.alpha, 1) } + func test_updatePickerState_updatesCategorySelection() { + let view = createSubject() + + view.configure( + sectionHeaderConfiguration: sectionHeaderConfiguration, + textColor: nil, + theme: theme, + transitionEnabled: false, + categories: testCategories, + selectedNewsfeedCategoryID: nil + ) + + view.updatePickerState(selectedNewsfeedCategoryID: "science", newsfeedCategoryPickerOffsetX: 0) + + XCTAssertTrue(button(withA11yID: AccessibilityIdentifiers.FirefoxHomepage.Pocket.allCategory, + in: view)?.isSelected == false) + XCTAssertTrue(button(withA11yID: AccessibilityIdentifiers.FirefoxHomepage.Pocket.allCategory + ".science", + in: view)?.isSelected == true) + } + + private var testCategories: [MerinoCategoryConfiguration] { + [ + MerinoCategoryConfiguration( + category: MerinoCategory( + feedID: "technology", + recommendations: [], + isBlocked: false, + isFollowed: false, + title: "Technology", + subtitle: nil, + receivedFeedRank: 2 + ) + ), + MerinoCategoryConfiguration( + category: MerinoCategory( + feedID: "science", + recommendations: [], + isBlocked: false, + isFollowed: false, + title: "Science", + subtitle: nil, + receivedFeedRank: 1 + ) + ), + ] + } + private func createSubject() -> NewsTransitionHeaderCell { let view = NewsTransitionHeaderCell(frame: CGRect(x: 0, y: 0, width: 320, height: 72)) trackForMemoryLeaks(view) @@ -85,6 +132,12 @@ final class NewsTransitionHeaderCellTests: XCTestCase { return allSubviews(in: view).compactMap { $0 as? UIStackView }.first } + private func button(withA11yID a11yID: String, in view: UIView) -> UIButton? { + return allSubviews(in: view) + .compactMap { $0 as? UIButton } + .first(where: { $0.accessibilityIdentifier == a11yID }) + } + private func allSubviews(in view: UIView) -> [UIView] { return view.subviews + view.subviews.flatMap { allSubviews(in: $0) } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/BookmarksSectionStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/BookmarksSectionStateTests.swift index f57da1bb74f98..b52d39fe85a4d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/BookmarksSectionStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/BookmarksSectionStateTests.swift @@ -15,7 +15,7 @@ final class BookmarksSectionStateTests: XCTestCase { override func setUp() async throws { try await super.setUp() mockProfile = MockProfile() - await DependencyHelperMock().bootstrapDependencies() + await DependencyHelperMock().bootstrapDependencies(injectedProfile: mockProfile) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) setupNimbusHomepageBookmarksSectionDefaultTesting(isEnabled: false) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/HeaderStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/HeaderStateTests.swift index 52d3066f38c05..9e822d303d5ec 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/HeaderStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/HeaderStateTests.swift @@ -16,6 +16,25 @@ final class HeaderStateTests: XCTestCase { XCTAssertFalse(initialState.showiPadSetup) } + @MainActor + func test_viewWillAppearAction_returnsExpectedState() { + let initialState = createSubject() + let reducer = headerReducer() + + let newState = reducer( + initialState, + HomepageAction( + showiPadSetup: true, + windowUUID: .XCTestDefaultUUID, + actionType: HomepageActionType.viewWillAppear + ) + ) + + XCTAssertEqual(newState.windowUUID, .XCTestDefaultUUID) + XCTAssertFalse(newState.isPrivate) + XCTAssertTrue(newState.showiPadSetup) + } + @MainActor func test_initializeAction_returnsExpectedState() { let initialState = createSubject() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/JumpBackInSectionStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/JumpBackInSectionStateTests.swift index 9ecc1190f4ffa..3cf8c3b48b49d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/JumpBackInSectionStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/Redux/JumpBackInSectionStateTests.swift @@ -15,7 +15,7 @@ final class JumpBackInSectionStateTests: XCTestCase { override func setUp() async throws { try await super.setUp() mockProfile = MockProfile() - await DependencyHelperMock().bootstrapDependencies() + await DependencyHelperMock().bootstrapDependencies(injectedProfile: mockProfile) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) setupNimbusHomepageJumpBackInSectionDefaultTesting(isEnabled: false) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/StoryCategoryPickerViewTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/StoryCategoryPickerViewTests.swift index 11830390e3a16..8cea42305ce31 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/StoryCategoryPickerViewTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/StoryCategoryPickerViewTests.swift @@ -60,9 +60,9 @@ final class StoryCategoryPickerViewTests: XCTestCase { let view = createSubject() var selectedNewsfeedCategoryID: String? = "technology" - view.configure(categories: testCategories, selectedNewsfeedCategoryID: "technology") { newSelection in + view.configure(categories: testCategories, selectedNewsfeedCategoryID: "technology", onSelection: { newSelection in selectedNewsfeedCategoryID = newSelection - } + }) button(withA11yID: AccessibilityIdentifiers.FirefoxHomepage.Pocket.allCategory, in: view)? .sendActions(for: .touchUpInside) @@ -74,9 +74,9 @@ final class StoryCategoryPickerViewTests: XCTestCase { let view = createSubject() var selectedNewsfeedCategoryID: String? - view.configure(categories: testCategories, selectedNewsfeedCategoryID: nil) { newSelection in + view.configure(categories: testCategories, selectedNewsfeedCategoryID: nil, onSelection: { newSelection in selectedNewsfeedCategoryID = newSelection - } + }) button(withA11yID: AccessibilityIdentifiers.FirefoxHomepage.Pocket.allCategory + ".science", in: view)? .sendActions(for: .touchUpInside) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/TopSitesManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/TopSitesManagerTests.swift index 1acd831b5263b..b766d6b013674 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/TopSitesManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/TopSitesManagerTests.swift @@ -18,9 +18,11 @@ final class TopSitesManagerTests: XCTestCase { profile = MockProfile() mockNotificationCenter = MockNotificationCenter() LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) } override func tearDown() async throws { + DependencyHelperMock().reset() profile = nil mockNotificationCenter = nil try await super.tearDown() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsCallbackTelemetryTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsCallbackTelemetryTests.swift index fa48a6b448150..0c0b281287019 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsCallbackTelemetryTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsCallbackTelemetryTests.swift @@ -20,7 +20,7 @@ final class UnifiedAdsCallbackTelemetryTests: XCTestCase { override func setUp() async throws { try await super.setUp() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: MockProfile()) + DependencyHelperMock().bootstrapDependencies() setupNimbusAdsClientTesting(isEnabled: false) networking = MockUnifiedTileNetworking() logger = MockLogger() @@ -33,6 +33,7 @@ final class UnifiedAdsCallbackTelemetryTests: XCTestCase { logger = nil gleanWrapper = nil mockAdsClient = nil + DependencyHelperMock().reset() try await super.tearDown() } @@ -159,7 +160,6 @@ final class UnifiedAdsCallbackTelemetryTests: XCTestCase { } func testImpressionTelemetry_whenAdsClientDisabled_doesNotCallRecordImpression() { - setupNimbusAdsClientTesting(isEnabled: false) let subject = createSubject() subject.sendImpressionTelemetry(tileSite: tileSite, position: 1) @@ -167,7 +167,6 @@ final class UnifiedAdsCallbackTelemetryTests: XCTestCase { } func testClickTelemetry_whenAdsClientDisabled_doesNotCallRecordClick() { - setupNimbusAdsClientTesting(isEnabled: false) let subject = createSubject() subject.sendClickTelemetry(tileSite: tileSite, position: 1) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsProviderTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsProviderTests.swift index 49d7678a37c90..0d1b30723df0c 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsProviderTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage/UnifiedAds/UnifiedAdsProviderTests.swift @@ -22,17 +22,23 @@ class MockMozAdsClient: MozAdsClientProtocol, @unchecked Sendable { return "test-context-id" } - func recordClick(clickUrl: String) throws { + func recordClick(clickUrl: String, options: MozillaAppServices.MozAdsCallbackOptions?) throws { if let error = mockError { throw error } recordClickCalledWith = clickUrl } - func recordImpression(impressionUrl: String) throws { + func recordImpression(impressionUrl: String, options: MozillaAppServices.MozAdsCallbackOptions?) throws { if let error = mockError { throw error } recordImpressionCalledWith = impressionUrl } - func reportAd(reportUrl: String, reason: MozAdsReportReason) throws {} + func reportAd( + reportUrl: String, + reason: MozillaAppServices.MozAdsReportReason, + options: MozillaAppServices.MozAdsCallbackOptions? + ) throws { + // no-op for tests for now + } func requestImageAds( mozAdRequests: [MozAdsPlacementRequest], @@ -65,23 +71,23 @@ class UnifiedAdsProviderTests: XCTestCase { override func setUp() async throws { try await super.setUp() + DependencyHelperMock().bootstrapDependencies() + setupNimbusAdsClientTesting(isEnabled: false) TelemetryContextualIdentifier.setupContextId() mockAdsClient = MockMozAdsClient() - setupNimbusAdsClientTesting(isEnabled: false) networking = MockUnifiedTileNetworking() } override func tearDown() async throws { mockAdsClient = nil networking = nil + DependencyHelperMock().reset() try await super.tearDown() } private func setupNimbusAdsClientTesting(isEnabled: Bool) { FxNimbus.shared.features.adsClient.with { _, _ in - return AdsClient( - status: isEnabled - ) + AdsClient(status: isEnabled) } } @@ -175,6 +181,22 @@ class UnifiedAdsProviderTests: XCTestCase { } } + func testFetchTiles_whenDataHasExtraField_thenReturnsProperTile() { + networking.data = getData(from: tilesWithExtraField) + networking.response = getResponse(from: 200) + let subject = createSubject() + + subject.fetchTiles { result in + switch result { + case let .success(tiles): + XCTAssertEqual(tiles.count, 1) + XCTAssertEqual(tiles[0].name, "Test1") + default: + XCTFail("Expected success, got \(result) instead") + } + } + } + // MARK: - Cache func testCaching_whenCacheData_thenSucceedsFromCache() { @@ -392,6 +414,25 @@ class UnifiedAdsProviderTests: XCTestCase { } """ + let tilesWithExtraField = """ + { + "newtab_mobile_tile_1": [ + { + "format": "tile", + "url": "https://www.test1.com", + "callbacks": { + "click": "https://www.test2.com", + "impression": "https://www.test3.com" + }, + "image_url": "https://www.test4.com", + "name": "Test1", + "block_key": "12345", + "test": "test" + } + ] + } + """ + let invertedTiles = """ { "newtab_mobile_tile_2": [ diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Helpers/IntroScreenManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Helpers/IntroScreenManagerTests.swift index 81d68bc22bcca..1e7eb6368fbb7 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Helpers/IntroScreenManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Helpers/IntroScreenManagerTests.swift @@ -12,16 +12,18 @@ import OnboardingKit final class IntroScreenManagerTests: XCTestCase { var prefs: MockProfilePrefs! - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() prefs = MockProfilePrefs() let mockProfile = MockProfile(databasePrefix: "IntroScreenManagerTests_") LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) + await DependencyHelperMock().bootstrapDependencies(injectedProfile: mockProfile) } - override func tearDown() { + override func tearDown() async throws { + DependencyHelperMock().reset() prefs = nil - super.tearDown() + try await super.tearDown() } // MARK: - shouldShowIntroScreen Tests @@ -122,6 +124,7 @@ final class IntroScreenManagerTests: XCTestCase { // MARK: - Helper Methods private func setupNimbusFeatureFlags(enableModernUi: Bool, + shouldUseBrandRefreshConfiguration: Bool = true, shouldUseJapanConfiguration: Bool, enableVideoIntro: Bool = false) { FxNimbus.shared.features.onboardingFrameworkFeature.with { appContext, _ in @@ -133,6 +136,7 @@ final class IntroScreenManagerTests: XCTestCase { dismissable: false, enableModernUi: enableModernUi, enableVideoIntro: enableVideoIntro, + shouldUseBrandRefreshConfiguration: shouldUseBrandRefreshConfiguration, shouldUseJapanConfiguration: shouldUseJapanConfiguration ) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Library/Bookmarks/BookmarkPanelViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Library/Bookmarks/BookmarkPanelViewModelTests.swift index a5f667c34d86e..61b5452f1f346 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Library/Bookmarks/BookmarkPanelViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Library/Bookmarks/BookmarkPanelViewModelTests.swift @@ -17,10 +17,12 @@ final class BookmarksPanelViewModelTests: XCTestCase, LegacyFeatureFlaggable { try await super.setUp() profile = MockProfile() LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) } override func tearDown() async throws { profile = nil + DependencyHelperMock().reset() try await super.tearDown() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Merino/MerinoProviderTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Merino/MerinoProviderTests.swift index 5924de3e16b66..a0dc8abc7f963 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Merino/MerinoProviderTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Merino/MerinoProviderTests.swift @@ -71,15 +71,24 @@ private final class MockCache: CuratedRecommendationsCacheProtocol { final class MerinoProviderTests: XCTestCase, @unchecked Sendable { private let storiesFlag = PrefsKeys.UserFeatureFlagPrefs.ASPocketStories + private var profile: MockProfile! - override func setUp() { - super.setUp() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: MockProfile()) + override func setUp() async throws { + try await super.setUp() + profile = MockProfile() + LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) FxNimbus.shared.features.homepageRedesignFeature.with { _, _ in HomepageRedesignFeature(categoriesEnabled: false) } } + override func tearDown() async throws { + DependencyHelperMock().reset() + profile = nil + try await super.tearDown() + } + func testIncorrectLocalesAreNotSupported() { XCTAssertFalse(MerinoProvider.isLocaleSupported("en_BD")) XCTAssertFalse(MerinoProvider.isLocaleSupported("enCA")) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift index 864bbb0b0c9bc..9c051da7922f4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockProfile.swift @@ -319,10 +319,6 @@ final class MockProfile: Client.Profile, @unchecked Sendable { return } - public func removeTabFromCommandQueue(_ deviceId: String, url: URL) { - return - } - public func flushTabCommands(toDeviceId: String?) { return } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift index fbf8599a54f64..20e6401230e9e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Mocks/MockTabManager.swift @@ -16,8 +16,6 @@ class MockTabManager: TabManager { var selectedIndex = 0 var selectedTab: Tab? var selectedTabUUID: UUID? - var backupCloseTab: BackupCloseTab? - var backupCloseTabs = [Tab]() // Use to return in getTabForUUID() var tabForUUID: Tab? @@ -96,10 +94,6 @@ class MockTabManager: TabManager { func removeNormalTabsOlderThan(period: TabsDeletionPeriod, currentDate: Date) {} - func undoCloseAllTabs() {} - - func undoCloseTab() {} - func clearAllTabsHistory() {} func commitChanges() { @@ -136,8 +130,6 @@ class MockTabManager: TabManager { isPrivate: Bool, previousTabUUID: String) {} - func undoCloseAllTabsLegacy(recentlyClosedTabs: [Client.Tab], previousTabUUID: String, isPrivate: Bool) {} - @discardableResult func addTab(_ request: URLRequest?, afterTab: Tab?, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/NativeErrorPage/NativeErrorPageFeatureFlagTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/NativeErrorPage/NativeErrorPageFeatureFlagTests.swift index 1de06d59723b5..43909d5fc311f 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/NativeErrorPage/NativeErrorPageFeatureFlagTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/NativeErrorPage/NativeErrorPageFeatureFlagTests.swift @@ -9,16 +9,18 @@ import XCTest class NativeErrorPageFeatureFlagTests: XCTestCase { var subject: NativeErrorPageFeatureFlag! - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() let profile = MockProfile() LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) subject = NativeErrorPageFeatureFlag() } - override func tearDown() { + override func tearDown() async throws { subject = nil - super.tearDown() + DependencyHelperMock().reset() + try await super.tearDown() } func testFeatureFlag_WhenNativeErrorPageEnabled_ThenFeatureIsEnabled() { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/NimbusFeatureFlagsTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/NimbusFeatureFlagsTests.swift index 65b69aabfdab5..f4c78d9f30078 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/NimbusFeatureFlagsTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/NimbusFeatureFlagsTests.swift @@ -26,13 +26,6 @@ final class NimbusFeatureFlagsTests: XCTestCase { // MARK: - Tests - func testIsEnabled_delegatesToNimbusLayer() { - // The layer returns Nimbus defaults (from FxNimbus.shared). - // bottomSearchBar defaults to true in Nimbus config for developer builds. - let result = subject.isEnabled(.bottomSearchBar) - XCTAssertTrue(result) - } - func testIsEnabled_withDebugOverrideSet_returnsOverride() { // On developer builds, debug overrides take precedence. // .translation has a debugKey, so we can override it. @@ -65,9 +58,8 @@ final class NimbusFeatureFlagsTests: XCTestCase { func testMockConformance() { let mock = MockNimbusFeatureFlags() - mock.enabledFlags = [.bottomSearchBar, .translation] + mock.enabledFlags = [.translation] - XCTAssertTrue(mock.isEnabled(.bottomSearchBar)) XCTAssertTrue(mock.isEnabled(.translation)) XCTAssertFalse(mock.isEnabled(.reportSiteIssue)) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewControllerTests.swift index 7e00e6c85aed3..f555381452cfd 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewControllerTests.swift @@ -16,10 +16,9 @@ class SearchViewControllerTest: XCTestCase { override func setUp() async throws { try await super.setUp() - DependencyHelperMock().bootstrapDependencies() profile = MockProfile(firefoxSuggest: MockRustFirefoxSuggest()) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) - LegacyFeatureFlagsManager.shared.set(feature: .firefoxSuggestFeature, to: true) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) let mockSearchEngineProvider = MockSearchEngineProvider() searchEnginesManager = SearchEnginesManager( @@ -45,6 +44,7 @@ class SearchViewControllerTest: XCTestCase { } override func tearDown() async throws { + DependencyHelperMock().reset() profile = nil try await super.tearDown() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewModelTests.swift index 27266d5d1784b..44b079a3e3045 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Search/SearchViewModelTests.swift @@ -17,10 +17,9 @@ final class SearchViewModelTests: XCTestCase { override func setUp() async throws { try await super.setUp() - DependencyHelperMock().bootstrapDependencies() profile = MockProfile(firefoxSuggest: MockRustFirefoxSuggest()) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) - LegacyFeatureFlagsManager.shared.set(feature: .firefoxSuggestFeature, to: true) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) let mockSearchEngineProvider = MockSearchEngineProvider() mockDelegate = MockSearchDelegate() @@ -44,6 +43,7 @@ final class SearchViewModelTests: XCTestCase { } override func tearDown() async throws { + DependencyHelperMock().reset() profile = nil mockDelegate = nil try await super.tearDown() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchBarLocationSaverTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchBarLocationSaverTests.swift index c27e5449b1da2..e7c2962d42f37 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchBarLocationSaverTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/SearchBarLocationSaverTests.swift @@ -14,7 +14,7 @@ class SearchBarLocationSaverTests: XCTestCase { override func setUp() async throws { try await super.setUp() profile = MockProfile() - await DependencyHelperMock().bootstrapDependencies() + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/AI Controls/AIControlsModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/AI Controls/AIControlsModelTests.swift index c82888b3ae4b0..d5c3dae4dd6ad 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/AI Controls/AIControlsModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/AI Controls/AIControlsModelTests.swift @@ -21,8 +21,8 @@ class AIControlsModelTests: XCTestCase, StoreTestUtility { PrefsKeys.Settings.aiKillSwitchFeature: true ], prefix: "") mockProfile.prefs = mockPrefs - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: MockProfile()) - await DependencyHelperMock().bootstrapDependencies(injectedProfile: mockProfile) + LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) + DependencyHelperMock().bootstrapDependencies(injectedProfile: mockProfile) } override func tearDown() async throws { @@ -109,22 +109,6 @@ class AIControlsModelTests: XCTestCase, StoreTestUtility { } else { XCTFail("No pref value for ai kill switch feature") } - - if let prefVal = mockPrefs.boolForKey(PrefsKeys.Settings.translationsFeature) { - XCTAssertFalse(prefVal) - } else { - XCTFail("No pref value for translations feature") - } - - if let prefVal = mockPrefs.boolForKey(PrefsKeys.Summarizer.summarizeContentFeature) { - XCTAssertFalse(prefVal) - } else { - XCTFail("No pref value for translations feature") - } - - wait(for: [expectation], timeout: 1.0) - let action = try XCTUnwrap(mockStore.dispatchedActions.last as? TranslationSettingsViewAction) - XCTAssertFalse(try XCTUnwrap(action.newSettingValue)) } @MainActor @@ -141,19 +125,6 @@ class AIControlsModelTests: XCTestCase, StoreTestUtility { } else { XCTFail("No pref value for ai kill switch feature") } - - if let prefVal = mockPrefs.boolForKey(PrefsKeys.Summarizer.summarizeContentFeature) { - XCTAssertTrue(prefVal) - } else { - XCTFail("No pref value for translations feature") - } - - XCTAssertTrue(aiControlsModel.pageSummariesEnabled) - - wait(for: [expectation], timeout: 1.0) - let action = try XCTUnwrap(mockStore.dispatchedActions.last as? TranslationSettingsViewAction) - XCTAssertTrue(try XCTUnwrap(action.newSettingValue)) - XCTAssertTrue(aiControlsModel.translationEnabled) } @MainActor diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/HomePageSettingViewControllerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/HomePageSettingViewControllerTests.swift index 17ff67e56d496..7f70c2a9d7d27 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/HomePageSettingViewControllerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/HomePageSettingViewControllerTests.swift @@ -17,6 +17,7 @@ final class HomePageSettingViewControllerTests: XCTestCase { try await super.setUp() profile = MockProfile() LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + setIsWorldCupFeatureFlagEnabled(false) DependencyHelperMock().bootstrapDependencies() self.profile = MockProfile() self.delegate = MockSettingsDelegate() @@ -78,7 +79,34 @@ final class HomePageSettingViewControllerTests: XCTestCase { XCTAssertFalse(bookmarksSectionSettingValue) } + func testHomepageSettings_generateSettings_worldCupSectionDefaultValue_whenFFEnabled_isTrue() throws { + let subject = createSubject() + subject.profile = profile + setIsWorldCupFeatureFlagEnabled(true) + + let settingsList = subject.generateSettings() + + let customizeFirefoxHomeSettingsList = settingsList.first( + where: { + $0.title?.string == .Settings.Homepage.CustomizeFirefoxHome.Title + }) + + let worldCupSectionSetting = customizeFirefoxHomeSettingsList?.children.first( + where: { + ($0 as? BoolSetting)?.prefKey == PrefsKeys.HomepageSettings.WorldCupSection + }) as? BoolSetting + + let worldCupSectionSettingValue = try XCTUnwrap(worldCupSectionSetting?.getDefaultValue()) + + XCTAssertTrue(worldCupSectionSettingValue) + } + // MARK: - Helper + private func setIsWorldCupFeatureFlagEnabled(_ isEnabled: Bool) { + FxNimbus.shared.features.worldCupWidgetFeature.with { _, _ in + return WorldCupWidgetFeature(enabled: isEnabled) + } + } private func createSubject() -> HomePageSettingViewController { let subject = HomePageSettingViewController(prefs: profile.prefs, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/SearchBarSettingsViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/SearchBarSettingsViewModelTests.swift index 42e66371d15e5..7f58be42539f4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/SearchBarSettingsViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/SearchBarSettingsViewModelTests.swift @@ -20,19 +20,21 @@ class SearchBarSettingsViewModelTests: XCTestCase { prefs = profile.prefs prefs.clearAll() mockNotificationCenter = MockNotificationCenter() + DependencyHelperMock().bootstrapDependencies() } override func tearDown() async throws { prefs.clearAll() prefs = nil mockNotificationCenter = nil + DependencyHelperMock().reset() try await super.tearDown() } // MARK: Default func testDefaultSearchPosition() { let viewModel = createViewModel() - XCTAssertEqual(viewModel.searchBarPosition, .top) + XCTAssertEqual(viewModel.searchBarPosition, .bottom) } // MARK: Saved @@ -99,7 +101,7 @@ class SearchBarSettingsViewModelTests: XCTestCase { let viewModel = createViewModel() let searchBarPosition = viewModel.searchBarPosition - XCTAssertEqual(searchBarPosition, .top) + XCTAssertEqual(searchBarPosition, .bottom) XCTAssertEqual(mockNotificationCenter.postCallCount, 0) } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/ThemeSettingsStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/ThemeSettingsStateTests.swift index 60976079c8957..4d2f046277d1d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/ThemeSettingsStateTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/ThemeSettingsStateTests.swift @@ -26,7 +26,7 @@ final class ThemeSettingsStateTests: XCTestCase { let subject = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .dark, userBrightnessThreshold: 0.5, systemBrightness: 0.8 @@ -50,7 +50,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .dark, userBrightnessThreshold: 0.7, systemBrightness: 0.6 @@ -95,7 +95,7 @@ final class ThemeSettingsStateTests: XCTestCase { let initialState = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -105,7 +105,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -129,7 +129,7 @@ final class ThemeSettingsStateTests: XCTestCase { let initialState = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -139,7 +139,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -166,7 +166,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -190,7 +190,7 @@ final class ThemeSettingsStateTests: XCTestCase { let initialState = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -200,7 +200,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -227,7 +227,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .dark, userBrightnessThreshold: 0, systemBrightness: 1 @@ -251,7 +251,7 @@ final class ThemeSettingsStateTests: XCTestCase { let initialState = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .dark, userBrightnessThreshold: 0, systemBrightness: 1 @@ -261,7 +261,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -288,7 +288,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0.75, systemBrightness: 1 @@ -311,7 +311,7 @@ final class ThemeSettingsStateTests: XCTestCase { let initialState = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0.5, systemBrightness: 1 @@ -321,7 +321,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -348,7 +348,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 0.3 @@ -374,7 +374,7 @@ final class ThemeSettingsStateTests: XCTestCase { let newStateData = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1.0 @@ -397,7 +397,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state1 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .dark, userBrightnessThreshold: 0.5, systemBrightness: 0.8 @@ -406,7 +406,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state2 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .dark, userBrightnessThreshold: 0.5, systemBrightness: 0.8 @@ -420,7 +420,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state2 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: true, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -434,7 +434,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state2 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: true, + isAutomaticBrightnessEnabled: true, manualThemeSelected: .light, userBrightnessThreshold: 0, systemBrightness: 1 @@ -448,7 +448,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state2 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .dark, userBrightnessThreshold: 0, systemBrightness: 1 @@ -462,7 +462,7 @@ final class ThemeSettingsStateTests: XCTestCase { let state2 = ThemeSettingsState( windowUUID: .XCTestDefaultUUID, useSystemAppearance: false, - isAutomaticBrightnessEnable: false, + isAutomaticBrightnessEnabled: false, manualThemeSelected: .light, userBrightnessThreshold: 0.5, systemBrightness: 1 diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/TranslationSettingsMiddlewareTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/TranslationSettingsMiddlewareTests.swift index d16d8650e9520..eec019b099dd8 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/TranslationSettingsMiddlewareTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Settings/TranslationSettingsMiddlewareTests.swift @@ -204,7 +204,7 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { ) let expectation = XCTestExpectation(description: "wait for actions to dispatch") - expectation.expectedFulfillmentCount = 3 + expectation.expectedFulfillmentCount = 2 mockStore.dispatchCalled = { expectation.fulfill() } subject.translationSettingsProvider(mockStore.state, action) @@ -212,7 +212,7 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { wait(for: [expectation], timeout: 1.0) // Expects ToolbarAction + TranslationSettingsMiddlewareAction - XCTAssertEqual(mockStore.dispatchedActions.count, 3) + XCTAssertEqual(mockStore.dispatchedActions.count, 2) let toolbarAction = try XCTUnwrap(mockStore.dispatchedActions.first as? ToolbarAction) let toolbarActionType = try XCTUnwrap(toolbarAction.actionType as? ToolbarActionType) @@ -224,11 +224,6 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { XCTAssertEqual(settingsActionType, TranslationSettingsMiddlewareActionType.didUpdateSettings) XCTAssertEqual(settingsAction.isTranslationsEnabled, false) XCTAssertEqual(mockProfile.prefs.boolForKey(PrefsKeys.Settings.translationsFeature), false) - - let resetStorageAction = try XCTUnwrap(mockStore.dispatchedActions.last as? TranslationSettingsMiddlewareAction) - let resetStorageActionType = try XCTUnwrap(resetStorageAction.actionType as? TranslationSettingsMiddlewareActionType) - - XCTAssertEqual(resetStorageActionType, TranslationSettingsMiddlewareActionType.didResetStorage) subject.translationSettingsProvider = { _, _ in } } @@ -260,9 +255,12 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { mockProfile.prefs.setBool(true, forKey: PrefsKeys.Settings.translationsFeature) let expectation = XCTestExpectation(description: "wait for actions to dispatch") - expectation.expectedFulfillmentCount = 3 + expectation.expectedFulfillmentCount = 2 mockStore.dispatchCalled = { expectation.fulfill() } + let resetExpectation = XCTestExpectation(description: "reset storage was called") + mockModelsFetcher.resetStorageExpectation = resetExpectation + let subject = createSubject() let action = TranslationSettingsViewAction( windowUUID: .XCTestDefaultUUID, @@ -271,9 +269,10 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { subject.translationSettingsProvider(mockStore.state, action) - wait(for: [expectation], timeout: 1.0) + wait(for: [expectation, resetExpectation], timeout: 1.0) + + XCTAssertEqual(mockStore.dispatchedActions.count, 2) - XCTAssertEqual(mockStore.dispatchedActions.count, 3) subject.translationSettingsProvider = { _, _ in } } @@ -284,6 +283,10 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { expectation.assertForOverFulfill = true mockStore.dispatchCalled = { expectation.fulfill() } + let resetExpectation = XCTestExpectation(description: "reset storage was called") + mockModelsFetcher.resetStorageExpectation = resetExpectation + resetExpectation.isInverted = true + let subject = createSubject() let action = TranslationSettingsViewAction( windowUUID: .XCTestDefaultUUID, @@ -292,7 +295,7 @@ final class TranslationSettingsMiddlewareTests: XCTestCase, StoreTestUtility { subject.translationSettingsProvider(mockStore.state, action) - wait(for: [expectation], timeout: 2.0) + wait(for: [expectation, resetExpectation], timeout: 2.0) XCTAssertEqual(mockStore.dispatchedActions.count, 2) subject.translationSettingsProvider = { _, _ in } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryActivityItemProviderTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryActivityItemProviderTests.swift index dd7fc90f9eace..c39182b32b4b4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryActivityItemProviderTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryActivityItemProviderTests.swift @@ -6,6 +6,7 @@ import XCTest import Shared import UniformTypeIdentifiers import Glean +import Common @testable import Client @@ -15,15 +16,19 @@ final class ShareTelemetryActivityItemProviderTests: XCTestCase { let testSubtitle = "Test subtitle" private let testFileURL = URL(string: "file://some/file/url")! private let testWebURL = URL(string: "https://mozilla.org")! + private var profile: MockProfile! - override func setUp() { - super.setUp() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: MockProfile()) + override func setUp() async throws { + try await super.setUp() + profile = MockProfile() + LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) } - override func tearDown() { - UserDefaults.standard.removeObject(forKey: PrefsKeys.NimbusUserEnabledFeatureTestsOverride) - super.tearDown() + override func tearDown() async throws { + DependencyHelperMock().reset() + profile = nil + try await super.tearDown() } func testWithShareType_noShareMessage_callTelemetryOnly() throws { @@ -82,7 +87,8 @@ final class ShareTelemetryActivityItemProviderTests: XCTestCase { setupNimbusSentFromFirefoxTesting(isEnabled: testNimbusEnrollment, isTreatmentA: true) // Opt in the user preference - UserDefaults.standard.set(testUserOptIn, forKey: PrefsKeys.NimbusUserEnabledFeatureTestsOverride) + let userPreferences: UserFeaturePreferring = AppContainer.shared.resolve() + userPreferences.setSentFromFirefoxEnabled(testUserOptIn) let shareTelemetryActivityItemProvider = ShareTelemetryActivityItemProvider( shareTypeName: testShareType.typeName, @@ -113,7 +119,8 @@ final class ShareTelemetryActivityItemProviderTests: XCTestCase { setupNimbusSentFromFirefoxTesting(isEnabled: testNimbusEnrollment, isTreatmentA: true) // Opt in the user preference - UserDefaults.standard.set(testUserOptIn, forKey: PrefsKeys.NimbusUserEnabledFeatureTestsOverride) + let userPreferences: UserFeaturePreferring = AppContainer.shared.resolve() + userPreferences.setSentFromFirefoxEnabled(testUserOptIn) let shareTelemetryActivityItemProvider = ShareTelemetryActivityItemProvider( shareTypeName: testShareType.typeName, @@ -144,7 +151,8 @@ final class ShareTelemetryActivityItemProviderTests: XCTestCase { setupNimbusSentFromFirefoxTesting(isEnabled: testNimbusEnrollment, isTreatmentA: true) // Opt in the user preference - UserDefaults.standard.set(testUserOptIn, forKey: PrefsKeys.NimbusUserEnabledFeatureTestsOverride) + let userPreferences: UserFeaturePreferring = AppContainer.shared.resolve() + userPreferences.setSentFromFirefoxEnabled(testUserOptIn) let shareTelemetryActivityItemProvider = ShareTelemetryActivityItemProvider( shareTypeName: testShareType.typeName, @@ -175,7 +183,8 @@ final class ShareTelemetryActivityItemProviderTests: XCTestCase { setupNimbusSentFromFirefoxTesting(isEnabled: testNimbusEnrollment, isTreatmentA: true) // Opt in the user preference - UserDefaults.standard.set(testUserOptIn, forKey: PrefsKeys.NimbusUserEnabledFeatureTestsOverride) + let userPreferences: UserFeaturePreferring = AppContainer.shared.resolve() + userPreferences.setSentFromFirefoxEnabled(testUserOptIn) let shareTelemetryActivityItemProvider = ShareTelemetryActivityItemProvider( shareTypeName: testShareType.typeName, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeHelperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeHelperTests.swift index c9a5ff8ec0ccc..4f944d65db480 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeHelperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeHelperTests.swift @@ -18,8 +18,7 @@ class StartAtHomeHelperTests: XCTestCase { profile = MockProfile() tabManager = MockTabManager() - await DependencyHelperMock().bootstrapDependencies() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) } override func tearDown() async throws { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeMiddlewareTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeMiddlewareTests.swift index 26094141b0465..3abe30acdf6ab 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeMiddlewareTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StartAtHome/StartAtHomeMiddlewareTests.swift @@ -18,7 +18,6 @@ final class StartAtHomeMiddlewareTests: XCTestCase, StoreTestUtility { override func setUp() async throws { try await super.setUp() - DependencyHelperMock().bootstrapDependencies() mockProfile = MockProfile() mockTabManager = MockTabManager() mockTabManager.tabRestoreHasFinished = true @@ -26,7 +25,12 @@ final class StartAtHomeMiddlewareTests: XCTestCase, StoreTestUtility { wrappedManager: WindowManagerImplementation(), tabManager: mockTabManager ) - DependencyHelperMock().bootstrapDependencies(injectedWindowManager: mockWindowManager) + // Inject the mock profile so that `UserFeaturePreferring` resolved from the + // AppContainer reads from `mockProfile.prefs` (which the tests configure). + DependencyHelperMock().bootstrapDependencies( + injectedProfile: mockProfile, + injectedWindowManager: mockWindowManager, + ) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: mockProfile) setupStore() appState = setupAppState() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StatusBarOverlayTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StatusBarOverlayTests.swift index 348036037be1f..4b002ac5a6fa0 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/StatusBarOverlayTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/StatusBarOverlayTests.swift @@ -20,15 +20,17 @@ final class StatusBarOverlayTests: XCTestCase { override func setUp() async throws { try await super.setUp() self.profile = MockProfile() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) self.wallpaperManager = WallpaperManagerMock() self.notificationCenter = MockNotificationCenter() + LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) + DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) } override func tearDown() async throws { self.profile = nil self.wallpaperManager = nil self.notificationCenter = nil + DependencyHelperMock().reset() try await super.tearDown() } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Summarizer/SummarizerNimbusUtilsTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Summarizer/SummarizerNimbusUtilsTests.swift index e7b36d615f5fb..6d73d007eb2e7 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Summarizer/SummarizerNimbusUtilsTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Summarizer/SummarizerNimbusUtilsTests.swift @@ -11,20 +11,21 @@ final class SummarizerNimbusUtilsTests: XCTestCase { private let itTestLocale = Locale(identifier: "it") private let userDefaults = UserDefaults.standard - override func setUp() { - super.setUp() + override func setUp() async throws { + try await super.setUp() profile = MockProfile() + await DependencyHelperMock().bootstrapDependencies(injectedProfile: profile) // Set features to default values setHostedSummarizerFeature() setIsAppleIntelligenceAvailable() setLanguageExpansionFeature() setIsAppAttestAuthEnabled() - LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) } - override func tearDown() { + override func tearDown() async throws { profile = nil - super.tearDown() + DependencyHelperMock().reset() + try await super.tearDown() } // MARK: - isSummarizeFeatureToggledOn diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabManagement/TabManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabManagement/TabManagerTests.swift index 4c5e4d5db2e09..22cc1e81307b9 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabManagement/TabManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabManagement/TabManagerTests.swift @@ -153,31 +153,6 @@ final class TabManagerTests: XCTestCase { XCTAssertEqual(tab, addedTab) } - @MainActor - func testUndoCloseTab() { - let subject = createSubject() - let tab = Tab(profile: mockProfile, windowUUID: tabWindowUUID) - tab.url = URL(string: "https://mozilla.com/")! - XCTAssertEqual(subject.selectedIndex, -1) - subject.backupCloseTab = BackupCloseTab(tab: tab, isSelected: true) - subject.undoCloseTab() - XCTAssertEqual(subject.selectedIndex, 0) - } - - @MainActor - func testUndoCloseTabWithSelectedTab() { - let closedTab = Tab(profile: mockProfile, windowUUID: tabWindowUUID) - closedTab.url = URL(string: "https://mozilla.com/")! - let selectedTab = Tab(profile: mockProfile, windowUUID: tabWindowUUID) - selectedTab.url = URL(string: "https://mozilla.com/1")! - let subject = createSubject(tabs: [selectedTab]) - subject.selectTab(selectedTab) - XCTAssertEqual(subject.selectedIndex, 0) - subject.backupCloseTab = BackupCloseTab(tab: closedTab, isSelected: true) - subject.undoCloseTab() - XCTAssertEqual(subject.selectedIndex, 1) - } - // MARK: - Document pause - restore @MainActor func testSelectTab_pauseCurrentDocumentDownload() throws { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTests.swift index b08c6b312fd62..180d3c6cb23ed 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TabTests.swift @@ -540,6 +540,41 @@ class TabTests: XCTestCase { XCTAssertEqual(mockFileManager.removeItemAtURLCalled, session.count) } + // MARK: - HTTPS Navigation Policy + @MainActor + func testCreateWebview_whenHTTPSUpgradeEnabled_setsUpgradePolicy() async throws { + guard #available(iOS 18.2, *) else { + throw XCTSkip("preferredHTTPSNavigationPolicy requires iOS 18.2+") + } + + setHTTPSUpgradeFeature(isEnabled: true) + + let subject = createSubject() + subject.createWebview(configuration: WKWebViewConfiguration()) + + let policy = subject.webView?.configuration + .defaultWebpagePreferences? + .preferredHTTPSNavigationPolicy + XCTAssertEqual(policy, .automaticFallbackToHTTP) + await subject.close() + } + + @MainActor + func testCreateWebview_whenHTTPSUpgradeDisabled_doesNotSetUpgradePolicy() async throws { + guard #available(iOS 18.2, *) else { + throw XCTSkip("preferredHTTPSNavigationPolicy requires iOS 18.2+") + } + setHTTPSUpgradeFeature(isEnabled: false) + let subject = createSubject() + subject.createWebview(configuration: WKWebViewConfiguration()) + + let policy = subject.webView?.configuration + .defaultWebpagePreferences? + .preferredHTTPSNavigationPolicy + XCTAssertNotEqual(policy, .automaticFallbackToHTTP) + await subject.close() + } + // MARK: - Helpers @MainActor private func createSubject() -> Tab { @@ -552,6 +587,12 @@ class TabTests: XCTestCase { trackForMemoryLeaks(subject) return subject } + + private func setHTTPSUpgradeFeature(isEnabled: Bool = true) { + FxNimbus.shared.features.httpsUpgradeFeature.with { _, _ in + return HttpsUpgradeFeature(enabled: isEnabled) + } + } } // MARK: - MockLegacyTabDelegate diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/ToastTelemetryTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/ToastTelemetryTests.swift deleted file mode 100644 index 16a24de6f70d2..0000000000000 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/ToastTelemetryTests.swift +++ /dev/null @@ -1,56 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// 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 Foundation -import Glean -import XCTest - -@testable import Client - -final class ToastTelemetryTests: XCTestCase { - var mockGleanWrapper: MockGleanWrapper! - override func setUp() { - super.setUp() - - mockGleanWrapper = MockGleanWrapper() - } - - func testClosedSingleTabToastUndoSelected_callsGlean() throws { - let event = GleanMetrics.ToastsCloseSingleTab.undoTapped - let expectedMetricType = type(of: event) - let subject = createSubject() - - subject.undoClosedSingleTab() - - let savedMetric = try XCTUnwrap( - mockGleanWrapper.savedEvents.first as? EventMetricType - ) - let resultMetricType = type(of: savedMetric) - let debugMessage = TelemetryDebugMessage(expectedMetric: expectedMetricType, resultMetric: resultMetricType) - - XCTAssertEqual(mockGleanWrapper.recordEventNoExtraCalled, 1) - XCTAssert(resultMetricType == expectedMetricType, debugMessage.text) - } - - func testClosedAllTabsToastUndoSelected_callsGlean() throws { - let event = GleanMetrics.ToastsCloseAllTabs.undoTapped - let expectedMetricType = type(of: event) - let subject = createSubject() - - subject.undoClosedAllTabs() - - let savedMetric = try XCTUnwrap( - mockGleanWrapper.savedEvents.first as? EventMetricType - ) - let resultMetricType = type(of: savedMetric) - let debugMessage = TelemetryDebugMessage(expectedMetric: expectedMetricType, resultMetric: resultMetricType) - - XCTAssertEqual(mockGleanWrapper.recordEventNoExtraCalled, 1) - XCTAssert(resultMetricType == expectedMetricType, debugMessage.text) - } - - func createSubject() -> ToastTelemetry { - return ToastTelemetry(gleanWrapper: mockGleanWrapper) - } -} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift index 8191292d9ddd6..932247dcec5fa 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift @@ -281,10 +281,10 @@ final class AddressToolbarContainerModelTests: XCTestCase { isLoading: false, readerModeState: nil, canSummarize: false, - translationConfiguration: nil, didStartTyping: false, isEmptySearch: true, - alternativeSearchEngine: withSearchEngine) + alternativeSearchEngine: withSearchEngine, + translationConfiguration: nil) } private func createBasicNavigationBarState() -> NavigationBarState { diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockRemoteSettingsClient.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockRemoteSettingsClient.swift index a0f1d7b275a93..fbff24ab6db8e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockRemoteSettingsClient.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockRemoteSettingsClient.swift @@ -47,6 +47,11 @@ final class MockRemoteSettingsClient: RemoteSettingsClientProtocol, @unchecked S // no-op for tests for now } + func getLastModifiedTimestamp() -> UInt64? { + // This is currently not being used in tests + return 0 + } + func resetStorage() throws { resetStorageWasCalled = true } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationModelsFetcher.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationModelsFetcher.swift index b78ef874034ab..851ee06712ef1 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationModelsFetcher.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/MockTranslationModelsFetcher.swift @@ -8,6 +8,7 @@ import XCTest /// Minimal mock for TranslationModelsFetcherProtocol tests. This avoids going through remote settings. final class MockTranslationModelsFetcher: TranslationModelsFetcherProtocol, @unchecked Sendable { + var resetStorageExpectation: XCTestExpectation? var translatorWASMResult: Data? var modelsResult: Data? var modelBufferResult: Data? @@ -35,6 +36,6 @@ final class MockTranslationModelsFetcher: TranslationModelsFetcherProtocol, @unc } func resetStorage() async { - // no-op for now + resetStorageExpectation?.fulfill() } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/TranslationsMiddlewareTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/TranslationsMiddlewareTests.swift index e1a57da036659..bf8cca2c5d4d1 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/TranslationsMiddlewareTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TranslationsTests/TranslationsMiddlewareTests.swift @@ -295,27 +295,30 @@ final class TranslationsMiddlewareIntegrationTests: XCTestCase, StoreTestUtility actionType: TranslationsActionType.didSelectTargetLanguage ) - let expectation = XCTestExpectation( - description: "expect didStartTranslatingPage, translationCompleted action to be fired" - ) - expectation.expectedFulfillmentCount = 2 - mockStore.dispatchCalled = { expectation.fulfill() } + let didStartExpectation = XCTestExpectation(description: "didStartTranslatingPage dispatched") + let completedExpectation = XCTestExpectation(description: "translationCompleted dispatched") + mockStore.dispatchCalled = { [weak mockStore] in + guard let type = mockStore?.dispatchedActions.last?.actionType as? ToolbarActionType else { return } + switch type { + case .didStartTranslatingPage: didStartExpectation.fulfill() + case .translationCompleted: completedExpectation.fulfill() + default: break + } + } subject.translationsProvider(mockStore.state, action) - wait(for: [expectation], timeout: 1.0) - - XCTAssertEqual(mockStore.dispatchedActions.count, 2) - - let firstActionCalled = try XCTUnwrap(mockStore.dispatchedActions[0] as? ToolbarAction) - let firstActionType = try XCTUnwrap(firstActionCalled.actionType as? ToolbarActionType) + wait(for: [didStartExpectation, completedExpectation], timeout: 3.0, enforceOrder: true) - let secondActionCalled = try XCTUnwrap(mockStore.dispatchedActions[1] as? ToolbarAction) - let secondActionType = try XCTUnwrap(secondActionCalled.actionType as? ToolbarActionType) + let toolbarActions = mockStore.dispatchedActions.compactMap { $0 as? ToolbarAction } + let didStart = try XCTUnwrap(toolbarActions.first { + ($0.actionType as? ToolbarActionType) == .didStartTranslatingPage + }) + let completed = try XCTUnwrap(toolbarActions.first { + ($0.actionType as? ToolbarActionType) == .translationCompleted + }) - XCTAssertEqual(firstActionCalled.translationConfiguration?.state, .loading) - XCTAssertEqual(firstActionType, ToolbarActionType.didStartTranslatingPage) - XCTAssertEqual(secondActionCalled.translationConfiguration?.state, .active) - XCTAssertEqual(secondActionType, ToolbarActionType.translationCompleted) + XCTAssertEqual(didStart.translationConfiguration?.state, .loading) + XCTAssertEqual(completed.translationConfiguration?.state, .active) XCTAssertEqual(mockTranslationsTelemetry.translateButtonTappedCalledCount, 1) XCTAssertEqual(mockTranslationsTelemetry.lastActionType, .willTranslate) diff --git a/firefox-ios/firefox-ios-tests/Tests/FullFunctionalTestPlan.xctestplan b/firefox-ios/firefox-ios-tests/Tests/FullFunctionalTestPlan.xctestplan index b4f1cad8043de..24955fa473b43 100644 --- a/firefox-ios/firefox-ios-tests/Tests/FullFunctionalTestPlan.xctestplan +++ b/firefox-ios/firefox-ios-tests/Tests/FullFunctionalTestPlan.xctestplan @@ -111,7 +111,6 @@ "BookmarksTests\/testDesktopFoldersArePresent()", "BookmarksTests\/testLongTapRecentlySavedLink_tabTrayExperimentOff()", "BookmarksTests\/testLongTapRecentlySavedLink_tabTrayExperimentOn()", - "BookmarksTests\/testRecentlyBookmarked()", "BookmarksTests\/testUndoDeleteBookmark()", "BrowsingPDFTests\/testBookmarkPDF()", "BrowsingPDFTests\/testBookmarkPDF_TAE()", @@ -260,6 +259,7 @@ "PhotonActionSheetTests\/testPinToShortcuts_testPinToShortcuts()", "PhotonActionSheetTests\/testSharePageWithShareSheetOptions()", "PhotonActionSheetTests\/testSharePageWithShareSheetOptions_TAE()", + "PrivateBrowsingTest\/testAllPrivateTabsRestore()", "PrivateBrowsingTest\/testClosePrivateTabsOptionClosesPrivateTabsShortCutiPad()", "PrivateBrowsingTest\/testLongPressLinkOptionsPrivateMode_TAE()", "PrivateBrowsingTest\/testLongPressLinkOptionsPrivateMode_tabTrayExperimentOff()", @@ -309,8 +309,10 @@ "TabsTests\/testCloseAllTabsPrivateModeUndo_TAE()", "TabsTests\/testCloseAllTabsPrivateModeUndo_tabTrayExperimentOff()", "TabsTests\/testCloseAllTabsPrivateModeUndo_tabTrayExperimentOn()", + "TabsTests\/testCloseAllTabsUndo()", "TabsTests\/testCloseAllTabsUndo_TAE()", "TabsTests\/testCloseAllTabsUndo_tabTrayExperimentOff()", + "TabsTests\/testCloseOneTabUndo()", "TabsTests\/testCloseTabFromPageOptionsMenu()", "TabsTests\/testLongTapTabCounter_TAE()", "TabsTests\/testLongTapTabCounter_tabTrayExperimentOff()", @@ -319,6 +321,7 @@ "TabsTests\/testOpenNewTabLandscape_TAE()", "TabsTests\/testSwitchBetweenTabsNoPrivatePrivateToastButton()", "TabsTests\/testSwitchBetweenTabsToastButton()", + "TabsTests\/testTabTrayCloseMultipleTabs()", "TabsTests\/testopenNewTabLandscape()", "TabsTestsIphone\/testAddPrivateTabByLongPressTabsButton_TAE()", "TabsTestsIphone\/testAddPrivateTabByLongPressTabsButton_tabTrayExperimentOff()", diff --git a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nBaseSnapshotTests.swift b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nBaseSnapshotTests.swift index 045ec8846ac51..cbf8bc35e9eee 100644 --- a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nBaseSnapshotTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nBaseSnapshotTests.swift @@ -137,21 +137,42 @@ extension XCUIElement { coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } + func mozWaitElementHittable(timeout: Double) { + let predicate = NSPredicate(format: "exists == true && hittable == true") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self) + let result = XCTWaiter().wait(for: [expectation], timeout: timeout) + XCTAssertEqual(result, .completed, "Element did not become hittable in time.") + } + + func mozWaitForElementToExist(timeout: TimeInterval? = TIMEOUT) { + let startTime = Date() + guard exists else { + while !exists { + if let timeout = timeout, Date().timeIntervalSince(startTime) > timeout { + XCTFail("Timed out waiting for element \(self) to exist in \(timeout) seconds") + break + } + usleep(10000) + } + return + } + } + /// Waits for the UI element and then taps if it exists. func waitAndTap(timeout: TimeInterval? = TIMEOUT) { - L10nBaseSnapshotTests().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.tap() } /// Waits for the UI element and then taps and types the provided text if it exists. func tapAndTypeText(_ text: String, timeout: TimeInterval? = TIMEOUT) { - L10nBaseSnapshotTests().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.tap() self.typeText(text) } func pressWithRetry(duration: TimeInterval, timeout: TimeInterval = TIMEOUT, element: XCUIElement) { - L10nBaseSnapshotTests().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.press(forDuration: duration) var attempts = 5 while !element.exists && attempts > 0 { diff --git a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite1SnapshotTests.swift b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite1SnapshotTests.swift index a0858d5958c51..03f34d02b8758 100644 --- a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite1SnapshotTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite1SnapshotTests.swift @@ -99,7 +99,7 @@ class L10nSuite1SnapshotTests: L10nBaseSnapshotTests { @MainActor func testWebViewAuthenticationDialog() { - navigator.openURL("https://jigsaw.w3.org/HTTP/Basic/") + navigator.openURL("https://httpbin.org/basic-auth/user/passwd") mozWaitForElementToNotExist(app.staticTexts["XCUITests-Runner pasted from Fennec"]) // The auth dialog no longer shown in debugDescription. // The presence of the keyboard is a good indicator that the user/pass window appears. diff --git a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite2SnapshotTests.swift b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite2SnapshotTests.swift index 112f6bc00516b..303e36e4a2035 100644 --- a/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite2SnapshotTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/L10nSnapshotTests/L10nSuite2SnapshotTests.swift @@ -162,14 +162,15 @@ class L10nSuite2SnapshotTests: L10nBaseSnapshotTests { app.tables["Add Credential"].cells.element(boundBy: 2).waitAndTap(timeout: 5) tapKeyboardKey(key) app.navigationBars["Client.AddCredentialView"].buttons.element(boundBy: 1).waitAndTap(timeout: 5) + mozWaitForElementToExist(app.tables["Login List"], timeout: 15) - if app.sheets.firstMatch.exists { - app.sheets.firstMatch.buttons.firstMatch.waitAndTap() - mozWaitForElementToNotExist(app.sheets.firstMatch) - } + mozWaitForElementToExist(app.sheets.firstMatch) + mozWaitForElementToExist(app.sheets.firstMatch.buttons.element(boundBy: 1)) + app.sheets.firstMatch.buttons.firstMatch.waitAndTap() + mozWaitForElementToNotExist(app.sheets.firstMatch) snapshot("CreatedLoginView") - app.tables["Login List"].cells.element(boundBy: 2).waitAndTap() + app.tables["Login List"].cells.staticTexts["p"].waitAndTap() snapshot("CreatedLoginDetailedView") app.tables["Login Detail List"].cells.element(boundBy: 4).waitAndTap() diff --git a/firefox-ios/firefox-ios-tests/Tests/Smoketest.xctestplan b/firefox-ios/firefox-ios-tests/Tests/Smoketest.xctestplan index 80cd1fe95ad88..5df894c6bc956 100644 --- a/firefox-ios/firefox-ios-tests/Tests/Smoketest.xctestplan +++ b/firefox-ios/firefox-ios-tests/Tests/Smoketest.xctestplan @@ -145,14 +145,17 @@ "BookmarksTests\/testDeleteBookmarkSwiping()", "BookmarksTests\/testDeleteEmptyFolderInEditMode()", "BookmarksTests\/testDesktopFoldersArePresent()", + "BookmarksTests\/testDuplicateFoldersNames()", "BookmarksTests\/testEditBookmark()", "BookmarksTests\/testEditModeExitsOnlyWithDoneButton()", + "BookmarksTests\/testEditModeRemainsActive()", "BookmarksTests\/testLongTapRecentlySavedLink()", "BookmarksTests\/testLongTapRecentlySavedLink_tabTrayExperimentOff()", "BookmarksTests\/testLongTapRecentlySavedLink_tabTrayExperimentOn()", "BookmarksTests\/testNoFoldersInBookmarks()", "BookmarksTests\/testRecentlyBookmarked()", "BookmarksTests\/testValidateBookmarksOptions()", + "BookmarksTests\/testVerifyFolderSpecialCharacters()", "BrowsingPDFTests\/testBookmarkPDF_TAE()", "BrowsingPDFTests\/testLongPressOnPDFLink()", "BrowsingPDFTests\/testLongPressOnPDFLinkToAddToReadingList()", diff --git a/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/README.md b/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/README.md index 3db57c7ac7818..d35aff3c06fbd 100644 --- a/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/README.md +++ b/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/README.md @@ -5,10 +5,15 @@ you have these, make sure you're in the `SyncIntegrationTests` directory and run the following: ``` +$ export FXA_CI_SECRET=[secret] $ pipenv install --python 3.12 $ pipenv run pytest ``` +`FXA_CI_SECRET` can be obtained via the FxA team. This variable will be used +in the `fxa-ci` header to bypass reCAPTCHA-like challenges during automated +testing. + The tests will build and install the application to the simulator, which can cause a delay where there will be no feedback to the user. Also, note that each XCUITest that is executed will shutdown and **erase data from all available iOS diff --git a/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/conftest.py b/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/conftest.py index 71133372d258a..470bbebc2092e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/conftest.py +++ b/firefox-ios/firefox-ios-tests/Tests/SyncIntegrationTests/conftest.py @@ -73,8 +73,32 @@ def tps_log(pytestconfig, tmpdir): yield tps_log +@pytest.fixture(scope='session') +def fxa_ci_addon(tmpdir_factory): + secret = os.getenv('FXA_CI_SECRET', '') + addon_dir = tmpdir_factory.mktemp('fxa_ci_addon') + addon_dir.join('manifest.json').write(json.dumps({ + 'manifest_version': 2, + 'name': 'FxA CI Header', + 'version': '1.0', + 'browser_specific_settings': { + 'gecko': {'id': 'fxa-ci-header@mozilla.org'}, + }, + 'permissions': ['webRequest', 'webRequestBlocking', ''], + 'background': {'scripts': ['background.js']}, + })) + addon_dir.join('background.js').write( + 'browser.webRequest.onBeforeSendHeaders.addListener(' + '(d) => { d.requestHeaders.push({ name: "fxa-ci", value: %s });' + ' return { requestHeaders: d.requestHeaders }; },' + ' { urls: [""] }, ["blocking", "requestHeaders"]);' + % json.dumps(secret) + ) + return str(addon_dir) + + @pytest.fixture -def tps_profile(pytestconfig, tps_addon, tps_config, tps_log, fxa_urls): +def tps_profile(pytestconfig, tps_addon, fxa_ci_addon, tps_config, tps_log, fxa_urls): preferences = { 'app.update.enabled': False, 'security.turn_off_all_security_so_that_viruses_can_take_over_this_computer': True, @@ -112,7 +136,7 @@ def tps_profile(pytestconfig, tps_addon, tps_config, tps_log, fxa_urls): 'tps.seconds_since_epoch': int(time.time()), 'xpinstall.signatures.required': False } - profile = Profile(addons=[tps_addon], preferences=preferences) + profile = Profile(addons=[tps_addon, fxa_ci_addon], preferences=preferences) pytestconfig._profile = profile.profile yield profile diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/BaseTestCase.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/BaseTestCase.swift index 86d3bdcc0e9dc..08243ca72cbe2 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/BaseTestCase.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/BaseTestCase.swift @@ -730,12 +730,12 @@ extension XCUIElement { } /// Waits for the UI element and then taps if it exists. func waitAndTap(timeout: TimeInterval? = TIMEOUT) { - BaseTestCase().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.tap() } /// Waits for the UI element and then taps and types the provided text if it exists. func tapAndTypeText(_ text: String, timeout: TimeInterval? = TIMEOUT) { - BaseTestCase().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.tap() self.typeText(text) } @@ -753,7 +753,7 @@ extension XCUIElement { } func pressWithRetry(duration: TimeInterval, timeout: TimeInterval = TIMEOUT, element: XCUIElement) { - BaseTestCase().mozWaitForElementToExist(self, timeout: timeout) + self.mozWaitForElementToExist(timeout: timeout) self.press(forDuration: duration) if element.waitForExistence(timeout: 1.0) { return @@ -805,6 +805,27 @@ extension XCUIElement { .withOffset(CGVector(dx: centerX, dy: centerY + (elementBounds.size.height/2) * distance)) startCoordinate.press(forDuration: 0, thenDragTo: endCoordinate) } + + func mozWaitForElementToExist(timeout: TimeInterval? = TIMEOUT) { + let startTime = Date() + guard exists else { + while !exists { + if let timeout = timeout, Date().timeIntervalSince(startTime) > timeout { + XCTFail("Timed out waiting for element \(self) to exist in \(timeout) seconds") + break + } + usleep(10000) + } + return + } + } + + func mozWaitElementHittable(timeout: Double) { + let predicate = NSPredicate(format: "exists == true && hittable == true") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self) + let result = XCTWaiter().wait(for: [expectation], timeout: timeout) + XCTAssertEqual(result, .completed, "Element did not become hittable in time.") + } } extension XCUIElementQuery { diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/BookmarksTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/BookmarksTests.swift index 962dc4577c58d..b8a4c0b94c2a4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/BookmarksTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/BookmarksTests.swift @@ -211,15 +211,14 @@ class BookmarksTests: FeatureFlaggedTestBase { libraryScreen.assertNewFolderButtonExists(shouldExists: false) } - // https://mozilla.testrail.io/index.php?/cases/view/2306917 + // https://mozilla.testrail.io/index.php?/cases/view/3168649 func testDeleteBookmarkContextMenu() { app.launch() navigator.nowAt(NewTabScreen) - waitForTabsButton() + toolbarScreen.assertTabsButtonExists() navigator.goto(LibraryPanel_Bookmarks) // There is only one row in the bookmarks panel, which is the desktop folder - mozWaitForElementToExist(app.tables["Bookmarks List"]) - XCTAssertEqual(app.tables["Bookmarks List"].cells.count, 0) + libraryScreen.assertBookmarkListLabel(label: "Empty list") // Add a bookmark navigator.nowAt(LibraryPanel_Bookmarks) @@ -227,24 +226,19 @@ class BookmarksTests: FeatureFlaggedTestBase { navigator.goto(URLBarOpen) navigator.openURL(url_3) - waitForTabsButton() + toolbarScreen.assertTabsButtonExists() navigator.nowAt(BrowserTab) bookmark() // Check that it appears in Bookmarks panel navigator.goto(LibraryPanel_Bookmarks) - mozWaitForElementToExist(app.tables["Bookmarks List"]) + libraryScreen.assertBookmarkList() // Remove by long press and select option from context menu - app.tables.staticTexts.element(boundBy: 0).press(forDuration: 1) - mozWaitForElementToExist(app.tables["Context Menu"]) - app.tables["Context Menu"].cells.buttons["Remove Bookmark"].waitAndTap() - // Verify that there are only 1 cell (desktop bookmark folder) - mozWaitForElementToExist(app.staticTexts["No bookmarks yet"]) + libraryScreen.longPressAndSelectContextMenuOption(option: "Remove Bookmark") // Check that the bookmark was deleted by ensuring an element of the empty state is visible - let emptyStateSignInButtonIdentifier = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.emptyStateSignInButton - let bookmarkList = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.tableView - mozWaitForElementToExist(app.buttons[emptyStateSignInButtonIdentifier]) - XCTAssertEqual(app.tables[bookmarkList].label, "Empty list") + libraryScreen.assertBookmarkList() + libraryScreen.assertEmptyStateSignInButtonExists() + libraryScreen.assertBookmarkListLabel(label: "Empty list") } private func typeOnSearchBar(text: String) { @@ -411,6 +405,56 @@ class BookmarksTests: FeatureFlaggedTestBase { libraryScreen.assertBookmarkEmptyStateTextExists(shouldExist: false) } + // https://mozilla.testrail.io/index.php?/cases/view/3168589 + func testEditModeRemainsActive() { + app.launch() + navigator.goto(LibraryPanel_Bookmarks) + libraryScreen.assertBookmarkEmptyStateTextExists() + libraryScreen.tapEditButton() + // Close the Bookmark panel without tapping "Done" (using swipe) + app.swipeDown() + // The Bookmark panel closes + topSitesScreen.assertVisible() + // Navigate to Hamburger menu → Bookmarks, check the screen + navigator.nowAt(NewTabScreen) + navigator.goto(LibraryPanel_Bookmarks) + // Edit mode remains active + libraryScreen.assertNewFolderButtonExists() + // Tap to add a new folder + libraryScreen.tapBottomLeftButton() + // Tap back and lose the Bookmark panel without tapping "Done" (using swipe) + libraryScreen.tapBackButton() + app.swipeDown() + // The Bookmark panel closes + topSitesScreen.assertVisible() + // Navigate to Hamburger menu → Bookmarks, check the screen + navigator.nowAt(NewTabScreen) + navigator.goto(LibraryPanel_Bookmarks) + // Edit mode remains active + libraryScreen.assertNewFolderButtonExists() + } + + // https://mozilla.testrail.io/index.php?/cases/view/3168628 + func testVerifyFolderSpecialCharacters() { + app.launch() + navigator.goto(LibraryPanel_Bookmarks) + libraryScreen.addFreshNewFolder(text: "!@#$%^&*()_+") + libraryScreen.assertNewFreshFolderCreated(folderName: "!@#$%^&*()_+") + } + + // https://mozilla.testrail.io/index.php?/cases/view/3168629 + func testDuplicateFoldersNames() { + app.launch() + let folderName = "Sample Folder." + navigator.goto(LibraryPanel_Bookmarks) + libraryScreen.addFreshNewFolder(text: folderName) + libraryScreen.assertNewFreshFolderCreated(folderName: folderName) + libraryScreen.tapDoneButton() + libraryScreen.addFreshNewFolder(text: folderName) + libraryScreen.assertNewFreshFolderCreated(folderName: folderName) + libraryScreen.assertIdenticalFoldersNamesCreated(identifier: folderName, nrOfFolders: 2) + } + private func validateLongTapOptionsFromBookmarkLink() { // Go to "Recently saved" section and long tap on one of the links navigator.openURL(path(forTestPage: url_2["url"]!)) diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/ClipBoardTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/ClipBoardTests.swift index e1b95ad9c48cf..ff1b053edd5e4 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/ClipBoardTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/ClipBoardTests.swift @@ -84,7 +84,7 @@ class ClipBoardTests: BaseTestCase { urlBarAddress.press(forDuration: 1) } app.otherElements.buttons["Paste"].waitAndTap() - mozWaitForValueContains(urlBarAddress, value: "http://www.example.com/") + mozWaitForValueContains(urlBarAddress, value: "https://www.example.com/") } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/FxScreenGraph.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/FxScreenGraph.swift index e7100511fc82f..3f05e4ef8c7a3 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/FxScreenGraph.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/FxScreenGraph.swift @@ -142,7 +142,7 @@ let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") func navigationControllerBackAction(for app: XCUIApplication) -> () -> Void { return { let backButton = app.navigationBars.element(boundBy: 0).buttons.element(boundBy: 0) - BaseTestCase().mozWaitElementHittable(element: backButton, timeout: TIMEOUT) + backButton.mozWaitElementHittable(timeout: TIMEOUT) app.navigationBars.element(boundBy: 0).buttons.element(boundBy: 0).waitAndTap() } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift index 9a1de34ce4ad4..c5eab67751317 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/HistoryTests.swift @@ -448,7 +448,7 @@ class HistoryTests: BaseTestCase { } private func navigateToPage(isTabTrayOff: Bool = true) { - navigator.openURL("example.com") + navigator.openURL("https://example.com") waitUntilPageLoad() waitForTabsButton() navigator.goto(TabTray) diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift index cc5f9eccb3f5c..06b14ffd5d1da 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/AddressScreen.swift @@ -57,7 +57,7 @@ final class AddressScreen { } func reachEditAndRemoveAddress() { - app.collectionViews.cells.buttons.staticTexts.firstMatch.tapWithRetry() + app.collectionViews.cells.buttons[AccessibilityIdentifiers.Settings.Address.Addresses.addressCell].tapWithRetry() // Update the all addresses fields let buttonEdit = sel.BUTTON_EDIT.element(in: app) buttonEdit.waitAndTap() diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/LibraryScreen.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/LibraryScreen.swift index a12caec0fd762..c51cbbf56e20a 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/LibraryScreen.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PageScreens/LibraryScreen.swift @@ -21,6 +21,7 @@ final class LibraryScreen { private var saveButton: XCUIElement { sel.SAVE_BUTTON.element(in: app) } private var bookmarkFolderCell: XCUIElement { sel.BOOKMARKS_FOLDER.element(in: app) } private var deleteButton: XCUIElement { sel.DELETE_BUTTON.element(in: app) } + private var backButton: XCUIElement { sel.BACK_BUTTON.element(in: app) } func assertBookmarkExists(named name: String, timeout: TimeInterval = TIMEOUT_LONG) { let bookmarksTable = sel.BOOKMARKS_LIST.element(in: app) @@ -73,6 +74,11 @@ final class LibraryScreen { } } + func assertIdenticalFoldersNamesCreated(identifier: String, nrOfFolders: Int) { + let elements = app.staticTexts.matching(identifier: identifier) + XCTAssertEqual(elements.count, nrOfFolders, "Expected \(nrOfFolders) identical folder names") + } + func tapEditButton() { editButton.firstMatch.waitAndTap() } @@ -122,4 +128,15 @@ final class LibraryScreen { app.tables.cells.buttons["Remove \(folderName)"].waitAndTap() deleteButton.waitAndTap() } + + func tapBackButton() { + backButton.waitAndTap() + } + + func longPressAndSelectContextMenuOption(option: String) { + let tableContextMenu = app.tables["Context Menu"] + app.tables.staticTexts.element(boundBy: 0).press(forDuration: 1) + BaseTestCase().mozWaitForElementToExist(tableContextMenu) + tableContextMenu.cells.buttons[option].waitAndTap() + } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PrivateBrowsingTest.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PrivateBrowsingTest.swift index 5d629cd854705..3d45a8b03fae9 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/PrivateBrowsingTest.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/PrivateBrowsingTest.swift @@ -223,46 +223,45 @@ class PrivateBrowsingTest: BaseTestCase { // https://mozilla.testrail.io/index.php?/cases/view/2497357 func testAllPrivateTabsRestore() throws { // Several tabs opened in private tabs tray. Tap on the trashcan - if !iPad() { - let shouldSkipTest = true - try XCTSkipIf(shouldSkipTest, "Undo toast no longer available on iPhone") - } - navigator.nowAt(HomePanelsScreen) - navigator.toggleOn(userState.isPrivate, withAction: Action.ToggleExperimentPrivateMode) - for _ in 1...4 { - navigator.createNewTab() - } - waitForTabsButton() - navigator.goto(TabTray) - var numTab = app.otherElements[tabsTray].cells.count - XCTAssertEqual(4, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") - app.buttons[AccessibilityIdentifiers.TabTray.closeAllTabsButton].waitAndTap() - - // Validate Close All Tabs and Cancel options - mozWaitForElementToExist(app.buttons[AccessibilityIdentifiers.TabTray.deleteCloseAllButton]) - - // Tap on "Close All Tabs" - app.buttons[AccessibilityIdentifiers.TabTray.deleteCloseAllButton].firstMatch.waitAndTap() - if #unavailable(iOS 16) { - // Wait for the screen to refresh first. - mozWaitForElementToExist( - app.staticTexts["Firefox won’t remember any of your history or cookies, but new bookmarks will be saved."]) - } - // The private tabs are closed - waitForElementsToExist( - [ - app.staticTexts["Private Browsing"], - app.otherElements[tabsTray] - ] - ) - numTab = app.otherElements[tabsTray].cells.count - XCTAssertEqual(0, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") - - app.buttons["Undo"].waitAndTap() - - // All the private tabs are restored - numTab = app.otherElements[tabsTray].cells.count - XCTAssertEqual(4, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") + throw XCTSkip("Undo toast no longer available") + /* + navigator.nowAt(HomePanelsScreen) + navigator.toggleOn(userState.isPrivate, withAction: Action.ToggleExperimentPrivateMode) + for _ in 1...4 { + navigator.createNewTab() + } + waitForTabsButton() + navigator.goto(TabTray) + var numTab = app.otherElements[tabsTray].cells.count + XCTAssertEqual(4, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") + app.buttons[AccessibilityIdentifiers.TabTray.closeAllTabsButton].waitAndTap() + + // Validate Close All Tabs and Cancel options + mozWaitForElementToExist(app.buttons[AccessibilityIdentifiers.TabTray.deleteCloseAllButton]) + + // Tap on "Close All Tabs" + app.buttons[AccessibilityIdentifiers.TabTray.deleteCloseAllButton].firstMatch.waitAndTap() + if #unavailable(iOS 16) { + // Wait for the screen to refresh first. + mozWaitForElementToExist( + app.staticTexts["Firefox won’t remember any of your history or cookies, but new bookmarks will be saved."]) + } + // The private tabs are closed + waitForElementsToExist( + [ + app.staticTexts["Private Browsing"], + app.otherElements[tabsTray] + ] + ) + numTab = app.otherElements[tabsTray].cells.count + XCTAssertEqual(0, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") + + app.buttons["Undo"].waitAndTap() + + // All the private tabs are restored + numTab = app.otherElements[tabsTray].cells.count + XCTAssertEqual(4, numTab, "The number of counted tabs is not equal to \(String(describing: numTab))") + */ } // Smoketest diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/HistorySelectors.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/HistorySelectors.swift index 247d2f26206f4..ccf2735b5d76b 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/HistorySelectors.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/HistorySelectors.swift @@ -13,7 +13,7 @@ protocol HistorySelectorsSet { struct HistorySelectors: HistorySelectorsSet { private enum IDs { - static let exampleEntry = "http://example.com/" + static let exampleEntry = "https://example.com/" static let deleteButton = "Delete" static let emptyMsg = emptyRecentlyClosedMesg static let tableViewId = AccessibilityIdentifiers.LibraryPanels.HistoryPanel.tableView diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/LibrarySelectors.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/LibrarySelectors.swift index f140f5ba9ae73..f4d88f140e4b5 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/LibrarySelectors.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/Selectors/LibrarySelectors.swift @@ -15,6 +15,7 @@ protocol LibrarySelectorsSet { var TITLE_TEXT_FIELD: Selector { get } var BOOKMARKS_FOLDER: Selector { get } var SAVE_BUTTON: Selector { get } + var BACK_BUTTON: Selector { get } var all: [Selector] { get } } @@ -31,6 +32,7 @@ struct LibrarySelectors: LibrarySelectorsSet { static let titleTextFields = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.titleTextField static let bookmarksFolder = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.bookmarksFolder static let saveButton = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.saveButton + static let backButton = AccessibilityIdentifiers.Settings.Search.backButtoniOS26 } let BOOKMARKS_LIST = Selector.tableIdOrLabel( @@ -51,6 +53,12 @@ struct LibrarySelectors: LibrarySelectorsSet { groups: ["library", "bookmarks"] ) + let BACK_BUTTON = Selector.buttonId( + IDs.backButton, + description: "Boomark new folder back button", + groups: ["bookmark", "search"] + ) + let BOOKMARK_EMPTY_STATE = Selector.staticTextId( IDs.bookmarkEmptyState, description: "Empty state text in the bookmarks panel", @@ -95,5 +103,5 @@ struct LibrarySelectors: LibrarySelectorsSet { var all: [Selector] { [BOOKMARKS_LIST, DELETE_BUTTON, SIGN_IN_BUTTON, BOOKMARK_EMPTY_STATE, EDIT_BUTTON, BOTTOM_LEFT_BUTTON, TITLE_TEXT_FIELD, BOOKMARKS_FOLDER, - DONE_BUTTON] } + DONE_BUTTON, BACK_BUTTON] } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/ShareLongPressTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/ShareLongPressTests.swift index c7c062c789a4a..45ef8aaad4034 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/ShareLongPressTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/ShareLongPressTests.swift @@ -186,7 +186,7 @@ class ShareLongPressTests: FeatureFlaggedTestBase { private func longPressLinkAndSelectShareOption(option: String) { navigator.openURL(path(forTestPage: "test-example.html")) waitUntilPageLoad() - app.webViews["contentView"].links.element(boundBy: 0).press(forDuration: 1.5) + app.webViews[AccessibilityIdentifiers.Browser.WebView.contentView].links.element(boundBy: 0).press(forDuration: 1.5) mozWaitForElementToExist(app.buttons["Open in New Tab"]) app.buttons["Share Link"].waitAndTap() if #available(iOS 16, *) { diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/TabsTests.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/TabsTests.swift index 17f7bb30ac1a2..9ebc8d8afbabf 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/TabsTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/TabsTests.swift @@ -115,28 +115,27 @@ class TabsTests: BaseTestCase { // https://mozilla.testrail.io/index.php?/cases/view/2306865 func testCloseAllTabsUndo() throws { - if !iPad() { - let shouldSkipTest = true - try XCTSkipIf(shouldSkipTest, "Undo toast no longer available on iPhone") - } - toolBarScreen = ToolbarScreen(app: app) - firefoxHomePageScreen = FirefoxHomePageScreen(app: app) - tabTrayScreen = TabTrayScreen(app: app) - // A different tab than home is open to do the proper checks - navigator.openURL(path(forTestPage: "test-mozilla-org.html")) - waitUntilPageLoad() - toolBarScreen.assertTabsButtonExists() - navigator.nowAt(BrowserTab) - toolBarScreen.tapOnTabsButton() - tabTrayScreen.tapOnNewTabButton() - checkNumberOfTabsExpectedToBeOpen(expectedNumberOfTabsOpen: 2) - // Close all tabs, undo it and check that the number of tabs is correct - navigator.performAction(Action.AcceptRemovingAllTabs) - tabTrayScreen.undoRemovingAllTabs() - firefoxHomePageScreen.assertTopSitesItemCellExist() - navigator.nowAt(BrowserTab) - checkNumberOfTabsExpectedToBeOpen(expectedNumberOfTabsOpen: 2) - tabTrayScreen.waitForTabWithLabel(urlLabel) + throw XCTSkip("Undo toast no longer available") + /* + toolBarScreen = ToolbarScreen(app: app) + firefoxHomePageScreen = FirefoxHomePageScreen(app: app) + tabTrayScreen = TabTrayScreen(app: app) + // A different tab than home is open to do the proper checks + navigator.openURL(path(forTestPage: "test-mozilla-org.html")) + waitUntilPageLoad() + toolBarScreen.assertTabsButtonExists() + navigator.nowAt(BrowserTab) + toolBarScreen.tapOnTabsButton() + tabTrayScreen.tapOnNewTabButton() + checkNumberOfTabsExpectedToBeOpen(expectedNumberOfTabsOpen: 2) + // Close all tabs, undo it and check that the number of tabs is correct + navigator.performAction(Action.AcceptRemovingAllTabs) + tabTrayScreen.undoRemovingAllTabs() + firefoxHomePageScreen.assertTopSitesItemCellExist() + navigator.nowAt(BrowserTab) + checkNumberOfTabsExpectedToBeOpen(expectedNumberOfTabsOpen: 2) + tabTrayScreen.waitForTabWithLabel(urlLabel) + */ } // https://mozilla.testrail.io/index.php?/cases/view/2354473 @@ -327,30 +326,29 @@ class TabsTests: BaseTestCase { // https://mozilla.testrail.io/index.php?/cases/view/2306868 func testTabTrayCloseMultipleTabs() throws { - if !iPad() { - let shouldSkipTest = true - try XCTSkipIf(shouldSkipTest, "Undo toast no longer available on iPhone") - } - validateToastWhenClosingMultipleTabs() - // Choose to undo the action - app.buttons["Undo"].waitAndTap() - waitUntilPageLoad() - // Only the latest tab closed is restored - navigator.nowAt(BrowserTab) - waitForTabsButton() - navigator.goto(TabTray) - let tabsTrayCell = app.otherElements[tabsTray].cells - XCTAssertEqual(tabsTrayCell.count, 2) - mozWaitForElementToExist(app.buttons["2"]) - mozWaitForElementToExist(app.otherElements.cells.staticTexts[urlLabelExample]) - // Repeat for private browsing mode - navigator.performAction(Action.ToggleExperimentPrivateMode) - navigator.performAction(Action.OpenNewTabFromTabTray) - validateToastWhenClosingMultipleTabs() - // Choose to undo the action - app.buttons["Undo"].waitAndTap() - // Only the latest tab closed is restored - mozWaitForElementToExist(app.otherElements.cells.staticTexts[urlLabelExample]) + throw XCTSkip("Undo toast no longer available") + /* + validateToastWhenClosingMultipleTabs() + // Choose to undo the action + app.buttons["Undo"].waitAndTap() + waitUntilPageLoad() + // Only the latest tab closed is restored + navigator.nowAt(BrowserTab) + waitForTabsButton() + navigator.goto(TabTray) + let tabsTrayCell = app.otherElements[tabsTray].cells + XCTAssertEqual(tabsTrayCell.count, 2) + mozWaitForElementToExist(app.buttons["2"]) + mozWaitForElementToExist(app.otherElements.cells.staticTexts[urlLabelExample]) + // Repeat for private browsing mode + navigator.performAction(Action.ToggleExperimentPrivateMode) + navigator.performAction(Action.OpenNewTabFromTabTray) + validateToastWhenClosingMultipleTabs() + // Choose to undo the action + app.buttons["Undo"].waitAndTap() + // Only the latest tab closed is restored + mozWaitForElementToExist(app.otherElements.cells.staticTexts[urlLabelExample]) + */ } // Smoketest @@ -375,44 +373,43 @@ class TabsTests: BaseTestCase { // https://mozilla.testrail.io/index.php?/cases/view/2306867 func testCloseOneTabUndo() throws { - if !iPad() { - let shouldSkipTest = true - try XCTSkipIf(shouldSkipTest, "Undo toast no longer available on iPhone") - } - // Open a few tabs - waitForTabsButton() - navigator.openURL("http://localhost:\(serverPort)/test-fixture/find-in-page-test.html") - waitUntilPageLoad() - navigator.createNewTab() - navigator.openURL("http://localhost:\(serverPort)/test-fixture/test-example.html") - waitUntilPageLoad() - navigator.createNewTab() - navigator.openURL("localhost:\(serverPort)/test-fixture/test-mozilla-org.html") - waitUntilPageLoad() - waitForTabsButton() - navigator.goto(TabTray) - - // Experiment from #25337: "Undo" button no longer available on iPhone. - // Tap "x" - let secondTab = app.cells[AccessibilityIdentifiers.TabTray.tabCell+"_0_2"] - secondTab.buttons[StandardImageIdentifiers.Large.cross].tap() - mozWaitForElementToNotExist(secondTab) - app.buttons["Undo"].waitAndTap() - mozWaitForElementToExist(secondTab) - - // Long press tab. Tap "Close Tab" from the context menu - secondTab.press(forDuration: 2) - mozWaitForElementToExist(app.collectionViews.buttons["Close Tab"]) - app.collectionViews.buttons["Close Tab"].waitAndTap() - mozWaitForElementToNotExist(secondTab) - app.buttons["Undo"].waitAndTap() - mozWaitForElementToExist(secondTab) - - // Swipe tab - secondTab.swipeLeft() - mozWaitForElementToNotExist(secondTab) - app.buttons["Undo"].waitAndTap() - mozWaitForElementToExist(secondTab) + throw XCTSkip("Undo toast no longer available") + /* + // Open a few tabs + waitForTabsButton() + navigator.openURL("http://localhost:\(serverPort)/test-fixture/find-in-page-test.html") + waitUntilPageLoad() + navigator.createNewTab() + navigator.openURL("http://localhost:\(serverPort)/test-fixture/test-example.html") + waitUntilPageLoad() + navigator.createNewTab() + navigator.openURL("localhost:\(serverPort)/test-fixture/test-mozilla-org.html") + waitUntilPageLoad() + waitForTabsButton() + navigator.goto(TabTray) + + // Experiment from #25337: "Undo" button no longer available on iPhone. + // Tap "x" + let secondTab = app.cells[AccessibilityIdentifiers.TabTray.tabCell+"_0_2"] + secondTab.buttons[StandardImageIdentifiers.Large.cross].tap() + mozWaitForElementToNotExist(secondTab) + app.buttons["Undo"].waitAndTap() + mozWaitForElementToExist(secondTab) + + // Long press tab. Tap "Close Tab" from the context menu + secondTab.press(forDuration: 2) + mozWaitForElementToExist(app.collectionViews.buttons["Close Tab"]) + app.collectionViews.buttons["Close Tab"].waitAndTap() + mozWaitForElementToNotExist(secondTab) + app.buttons["Undo"].waitAndTap() + mozWaitForElementToExist(secondTab) + + // Swipe tab + secondTab.swipeLeft() + mozWaitForElementToNotExist(secondTab) + app.buttons["Undo"].waitAndTap() + mozWaitForElementToExist(secondTab) + */ } private func validateToastWhenClosingMultipleTabs() { diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerMiscellanousNavigation.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerMiscellanousNavigation.swift index a86ff63e8cf8f..e70eb2ce73240 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerMiscellanousNavigation.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerMiscellanousNavigation.swift @@ -77,7 +77,7 @@ func registerMiscellanousNavigation(in map: MMScreenGraph, app: XCU map.addScreenState(EnhancedTrackingProtection) { screenState in screenState.gesture(forAction: Action.SelectTrackersBlocked) { userState in let trackersLabel = AccessibilityIdentifiers.EnhancedTrackingProtection.MainScreen.trackersLabel - app.staticTexts[trackersLabel].tap(force: true) + app.staticTexts[trackersLabel].waitAndTap() } } } diff --git a/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerSettingsNavigation.swift b/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerSettingsNavigation.swift index 99d1422c75bee..bc5b4c6395771 100644 --- a/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerSettingsNavigation.swift +++ b/firefox-ios/firefox-ios-tests/Tests/XCUITests/registerSettingsNavigation.swift @@ -178,7 +178,7 @@ func registerSettingsNavigation(in map: MMScreenGraph, app: XCUIApp let customengineurlTextView = tablesQuery.textViews["customEngineUrl"] let pasteAction = app.staticTexts["Paste"] UIPasteboard.general.string = searchEngineUrl - BaseTestCase().mozWaitForElementToExist(customengineurlTextView) + customengineurlTextView.mozWaitForElementToExist() customengineurlTextView.pressWithRetry(duration: 1.5, element: pasteAction) pasteAction.waitAndTap() } diff --git a/firefox-ios/nimbus-features/httpsUpgradeFeature.yaml b/firefox-ios/nimbus-features/httpsUpgradeFeature.yaml new file mode 100644 index 0000000000000..6b01264cd53c6 --- /dev/null +++ b/firefox-ios/nimbus-features/httpsUpgradeFeature.yaml @@ -0,0 +1,18 @@ +# The configuration for the automatic https upgrade feature +features: + https-upgrade-feature: + description: > + This feature enables automatic https upgrade. This is only available for 18.2+. + variables: + enabled: + description: > + Enables automatic https upgrade. + type: Boolean + default: false + defaults: + - channel: beta + value: + enabled: true + - channel: developer + value: + enabled: true diff --git a/firefox-ios/nimbus-features/searchFeature.yaml b/firefox-ios/nimbus-features/searchFeature.yaml index f82862a0f54af..fc881812d2567 100644 --- a/firefox-ios/nimbus-features/searchFeature.yaml +++ b/firefox-ios/nimbus-features/searchFeature.yaml @@ -13,23 +13,14 @@ features: awesome-bar: use-page-content: false search-highlights: false - position: - is-position-feature-enabled: true - is-bottom: false - channel: developer value: awesome-bar: search-highlights: false - position: - is-position-feature-enabled: true - is-bottom: false - channel: beta value: awesome-bar: search-highlights: false - position: - is-position-feature-enabled: true - is-bottom: false objects: AwesomeBar: @@ -47,23 +38,3 @@ objects: description: "Whether or not search highlights are enabled" type: Boolean default: false - position: - description: > - This property defines whether or not the feature is - enabled, and the position of the search bar - type: SearchBarPositionFeature - default: - is-position-feature-enabled: true - is-bottom: true - - SearchBarPositionFeature: - description: "The configuration for the bottom search bar on the homescreen" - fields: - is-position-feature-enabled: - type: Boolean - description: Whether or not the feature is enabled - default: true - is-bottom: - type: Boolean - description: Whether or not the default position is at the bottom - default: true diff --git a/firefox-ios/nimbus-features/translationsFeature.yaml b/firefox-ios/nimbus-features/translationsFeature.yaml index 6e2f230e1d7f4..4d59c64a541cd 100644 --- a/firefox-ios/nimbus-features/translationsFeature.yaml +++ b/firefox-ios/nimbus-features/translationsFeature.yaml @@ -8,7 +8,7 @@ features: description: > Whether or not to enable translations feature. type: Boolean - default: false + default: true languagePickerEnabled: description: > Whether or not to enable the translations language picker (Phase 2). @@ -17,9 +17,9 @@ features: defaults: - channel: beta value: - enabled: false + enabled: true languagePickerEnabled: false - channel: developer value: - enabled: false + enabled: true languagePickerEnabled: false diff --git a/firefox-ios/nimbus-features/worldCupWidgetFeature.yaml b/firefox-ios/nimbus-features/worldCupWidgetFeature.yaml new file mode 100644 index 0000000000000..1f7f07d5be730 --- /dev/null +++ b/firefox-ios/nimbus-features/worldCupWidgetFeature.yaml @@ -0,0 +1,18 @@ +# The configuration for the worldCupWidgetFeature feature +features: + world-cup-widget-feature: + description: > + The feature flag to manage the roll out of the world cup widget feature + variables: + enabled: + description: > + Whether or not this feature is enabled + type: Boolean + default: false + defaults: + - channel: beta + value: + enabled: false + - channel: developer + value: + enabled: false \ No newline at end of file diff --git a/firefox-ios/nimbus.fml.yaml b/firefox-ios/nimbus.fml.yaml index 758ccf3791ccf..e6ee3e0c4a055 100644 --- a/firefox-ios/nimbus.fml.yaml +++ b/firefox-ios/nimbus.fml.yaml @@ -28,6 +28,7 @@ include: - nimbus-features/hntSponsoredShortcutsFeature.yaml - nimbus-features/homepageRedesignFeature.yaml - nimbus-features/hostedSummarizerFeature.yaml + - nimbus-features/httpsUpgradeFeature.yaml - nimbus-features/improvedAppStoreReviewTriggerFeature.yaml - nimbus-features/messagingFeature.yaml - nimbus-features/microsurveyFeature.yaml @@ -56,3 +57,4 @@ include: - nimbus-features/trackingProtectionRefactor.yaml - nimbus-features/translationsFeature.yaml - nimbus-features/trendingSearchesFeature.yaml + - nimbus-features/worldCupWidgetFeature.yaml diff --git a/focus-ios/version.xcconfig b/focus-ios/version.xcconfig index ccbf12b88fe0c..47edd504f9d8d 100644 --- a/focus-ios/version.xcconfig +++ b/focus-ios/version.xcconfig @@ -1 +1 @@ -APP_VERSION = 150.2 +APP_VERSION = 150.3 diff --git a/latest_acorn_release.json b/latest_acorn_release.json index 3e1de332899e5..0402197f745ce 100644 --- a/latest_acorn_release.json +++ b/latest_acorn_release.json @@ -1,42 +1,42 @@ { - "url": "https://api.github.com/repos/FirefoxUX/acorn-icons/releases/307651504", - "assets_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/releases/307651504/assets", - "upload_url": "https://uploads.github.com/repos/FirefoxUX/acorn-icons/releases/307651504/assets{?name,label}", - "html_url": "https://github.com/FirefoxUX/acorn-icons/releases/tag/v1.71.0", - "id": 307651504, + "url": "https://api.github.com/repos/FirefoxUX/acorn-icons/releases/311870310", + "assets_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/releases/311870310/assets", + "upload_url": "https://uploads.github.com/repos/FirefoxUX/acorn-icons/releases/311870310/assets{?name,label}", + "html_url": "https://github.com/FirefoxUX/acorn-icons/releases/tag/v1.73.1", + "id": 311870310, "author": { - "login": "maggie-atkinson", - "id": 175737951, - "node_id": "U_kgDOCnmMXw", - "avatar_url": "https://avatars.githubusercontent.com/u/175737951?v=4", + "login": "cwzilla", + "id": 87655586, + "node_id": "MDQ6VXNlcjg3NjU1NTg2", + "avatar_url": "https://avatars.githubusercontent.com/u/87655586?v=4", "gravatar_id": "", - "url": "https://api.github.com/users/maggie-atkinson", - "html_url": "https://github.com/maggie-atkinson", - "followers_url": "https://api.github.com/users/maggie-atkinson/followers", - "following_url": "https://api.github.com/users/maggie-atkinson/following{/other_user}", - "gists_url": "https://api.github.com/users/maggie-atkinson/gists{/gist_id}", - "starred_url": "https://api.github.com/users/maggie-atkinson/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/maggie-atkinson/subscriptions", - "organizations_url": "https://api.github.com/users/maggie-atkinson/orgs", - "repos_url": "https://api.github.com/users/maggie-atkinson/repos", - "events_url": "https://api.github.com/users/maggie-atkinson/events{/privacy}", - "received_events_url": "https://api.github.com/users/maggie-atkinson/received_events", + "url": "https://api.github.com/users/cwzilla", + "html_url": "https://github.com/cwzilla", + "followers_url": "https://api.github.com/users/cwzilla/followers", + "following_url": "https://api.github.com/users/cwzilla/following{/other_user}", + "gists_url": "https://api.github.com/users/cwzilla/gists{/gist_id}", + "starred_url": "https://api.github.com/users/cwzilla/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/cwzilla/subscriptions", + "organizations_url": "https://api.github.com/users/cwzilla/orgs", + "repos_url": "https://api.github.com/users/cwzilla/repos", + "events_url": "https://api.github.com/users/cwzilla/events{/privacy}", + "received_events_url": "https://api.github.com/users/cwzilla/received_events", "type": "User", "user_view_type": "public", "site_admin": false }, - "node_id": "RE_kwDOI1CID84SVmOw", - "tag_name": "v1.71.0", + "node_id": "RE_kwDOI1CID84SlsNm", + "tag_name": "v1.73.1", "target_commitish": "main", - "name": "v1.71.0", + "name": "v1.73.1", "draft": false, "immutable": false, "prerelease": false, - "created_at": "2026-04-10T17:07:27Z", - "updated_at": "2026-04-10T17:10:33Z", - "published_at": "2026-04-10T17:10:33Z", + "created_at": "2026-04-21T17:41:24Z", + "updated_at": "2026-04-21T17:42:10Z", + "published_at": "2026-04-21T17:42:10Z", "assets": [], - "tarball_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/tarball/v1.71.0", - "zipball_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/zipball/v1.71.0", - "body": "# [Changelog]\r\n### [1.71.0]\r\n### Added\r\n#### Desktop\r\n\r\n- SVG\r\n - site-restrictions-16.svg\r\n\r\n#### Mobile\r\n\r\n- PDF\r\n - pinExtraSmall.pdf\r\n\r\n- SVG\r\n - pin-8.svg\r\n\r\n- XML\r\n - ic_pin_8.xml" + "tarball_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/tarball/v1.73.1", + "zipball_url": "https://api.github.com/repos/FirefoxUX/acorn-icons/zipball/v1.73.1", + "body": "# [Changelog]\r\n### [1.73.1]\r\n### Removed\r\n#### Desktop\r\n- SVG\r\n - pdf-16.svg\r\n - smart-window-color-16.svg" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e71271d6052e7..48e8cef7fd33f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "license": "MPL-2.0", "dependencies": { "@mozilla/readability": "^0.6.0", - "darkreader": "^4.9.120", - "dompurify": "^3.3.3", + "darkreader": "^4.9.89", + "dompurify": "^3.3.2", "page-metadata-parser": "1.1.4" }, "devDependencies": { @@ -168,9 +168,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "license": "MIT", "dependencies": { @@ -359,23 +359,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { @@ -1543,7 +1543,6 @@ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true, - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -1618,32 +1617,6 @@ "node": ">=14.0.0" } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -1681,13 +1654,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/trusted-types": { @@ -1912,9 +1885,9 @@ "license": "Apache-2.0" }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1938,11 +1911,10 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1973,9 +1945,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { @@ -2001,7 +1973,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, - "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -2027,14 +1998,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { @@ -2056,13 +2027,13 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -2072,20 +2043,16 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/baseline-browser-mapping": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", - "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/big.js": { @@ -2093,15 +2060,14 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, - "license": "MIT", "engines": { "node": "*" } }, "node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2150,9 +2116,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true, "funding": [ { @@ -2171,11 +2137,13 @@ "license": "CC-BY-4.0" }, "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "dev": true, - "license": "MIT", + "dependencies": { + "tslib": "^1.9.0" + }, "engines": { "node": ">=6.0" } @@ -2185,7 +2153,6 @@ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, - "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -2196,11 +2163,10 @@ } }, "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true }, "node_modules/commander": { "version": "2.20.3", @@ -2212,9 +2178,8 @@ "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -2224,9 +2189,9 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2253,20 +2218,15 @@ } }, "node_modules/darkreader": { - "version": "4.9.120", - "resolved": "https://registry.npmjs.org/darkreader/-/darkreader-4.9.120.tgz", - "integrity": "sha512-BYjP9rsFzwtO2KDXfzqGZHBfo9LAp7azyRR4UIj0/n8uls0C0J08Rhh5g05BZU/CARvDmMf+LQXM9PoPxO1M4A==", - "license": "MIT", + "version": "4.9.89", + "resolved": "https://registry.npmjs.org/darkreader/-/darkreader-4.9.89.tgz", + "integrity": "sha512-mO/HFu69+U1szlAfkhW+1P4IcSeNV9Su6JD3zTKFyg61b5GMWY70lkBimD4NEDpY6DJ4Ks9kFFpENeG/UcsKYw==", "dependencies": { "malevic": "0.20.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/darkreader/donate" - }, - "optionalDependencies": { - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0" } }, "node_modules/debug": { @@ -2288,18 +2248,20 @@ } }, "node_modules/dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", - "license": "(MPL-2.0 OR Apache-2.0)", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "node_modules/electron-to-chromium": { - "version": "1.5.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", - "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true, "license": "ISC" }, @@ -2308,15 +2270,14 @@ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "license": "MIT", "dependencies": { @@ -2362,7 +2323,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -2376,7 +2336,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2389,7 +2348,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -2399,7 +2357,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -2419,7 +2376,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8.x" } @@ -2428,15 +2384,13 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/fast-uri": { "version": "3.1.0", @@ -2456,21 +2410,16 @@ "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true }, "node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, - "license": "MIT", "dependencies": { "commondir": "^1.0.1", "make-dir": "^3.0.2", @@ -2488,7 +2437,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -2497,22 +2445,11 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "node_modules/function-bind": { "version": "1.1.2", @@ -2529,7 +2466,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2593,11 +2529,10 @@ } }, "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -2615,10 +2550,8 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2628,15 +2561,13 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.10" } @@ -2662,7 +2593,6 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -2680,9 +2610,8 @@ "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2726,22 +2655,19 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -2754,7 +2680,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2778,7 +2703,6 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, - "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -2793,7 +2717,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -2823,7 +2746,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, - "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -2837,8 +2759,7 @@ "node_modules/malevic": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/malevic/-/malevic-0.20.2.tgz", - "integrity": "sha512-s44yEUyfDaONt7nPT7NDQ+Z2oAswErG70ok2Q95bJFh1Bdcn4dZQVMrLE02ZIjTtYfQ/LFOVxF+yB3bdGw/GtQ==", - "license": "MIT" + "integrity": "sha512-s44yEUyfDaONt7nPT7NDQ+Z2oAswErG70ok2Q95bJFh1Bdcn4dZQVMrLE02ZIjTtYfQ/LFOVxF+yB3bdGw/GtQ==" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -2852,7 +2773,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2862,7 +2782,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2875,7 +2794,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2894,22 +2812,20 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -2919,7 +2835,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -2935,7 +2850,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -2948,7 +2862,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -2956,15 +2869,13 @@ "node_modules/page-metadata-parser": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/page-metadata-parser/-/page-metadata-parser-1.1.4.tgz", - "integrity": "sha512-TbPNw7GddbHs4c2DyYinFvh51BVsaMfdrweeylzGlg8qeuzALGxq2NF+6jbmeKc7DnU2BZRDOuWNnEjDwUSqRQ==", - "license": "MPL-2.0" + "integrity": "sha512-TbPNw7GddbHs4c2DyYinFvh51BVsaMfdrweeylzGlg8qeuzALGxq2NF+6jbmeKc7DnU2BZRDOuWNnEjDwUSqRQ==" }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -2983,8 +2894,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/picocolors": { "version": "1.1.1", @@ -2998,7 +2908,6 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, - "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -3007,11 +2916,10 @@ } }, "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -3027,7 +2935,6 @@ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", "dev": true, - "license": "MIT", "dependencies": { "resolve": "^1.9.0" }, @@ -3129,7 +3036,6 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, @@ -3142,7 +3048,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -3152,7 +3057,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.5", "ajv": "^6.12.4", @@ -3181,7 +3085,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -3191,7 +3094,6 @@ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, - "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, @@ -3264,7 +3166,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3273,9 +3174,9 @@ } }, "node_modules/tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { @@ -3287,9 +3188,9 @@ } }, "node_modules/terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3306,11 +3207,10 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", "dev": true, - "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -3340,9 +3240,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { @@ -3396,10 +3296,16 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -3483,7 +3389,6 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -3503,9 +3408,9 @@ } }, "node_modules/webpack": { - "version": "5.105.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", - "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "license": "MIT", "dependencies": { @@ -3515,11 +3420,11 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", + "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", + "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -3531,9 +3436,9 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -3604,20 +3509,17 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 10" } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, - "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", - "flat": "^5.0.2", "wildcard": "^2.0.0" }, "engines": { @@ -3625,9 +3527,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, "license": "MIT", "engines": { @@ -3635,9 +3537,9 @@ } }, "node_modules/webpack/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { @@ -3708,18 +3610,16 @@ } }, "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "node_modules/yallist": { "version": "3.1.1", @@ -3832,9 +3732,9 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.28.6", @@ -3958,19 +3858,19 @@ } }, "@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "requires": { "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/types": "^7.28.6" } }, "@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "requires": { "@babel/types": "^7.29.0" @@ -4747,18 +4647,6 @@ "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==" }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", - "optional": true - }, "@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -4792,12 +4680,12 @@ "dev": true }, "@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "requires": { - "undici-types": "~7.18.0" + "undici-types": "~7.16.0" } }, "@types/trusted-types": { @@ -4988,9 +4876,9 @@ "dev": true }, "acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "acorn-import-phases": { @@ -5001,9 +4889,9 @@ "requires": {} }, "ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -5022,9 +4910,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.3", @@ -5061,13 +4949,13 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "dev": true, "requires": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" } }, @@ -5082,12 +4970,12 @@ } }, "babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.8" + "@babel/helper-define-polyfill-provider": "^0.6.6" } }, "balanced-match": { @@ -5097,9 +4985,9 @@ "dev": true }, "baseline-browser-mapping": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", - "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true }, "big.js": { @@ -5109,9 +4997,9 @@ "dev": true }, "brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -5137,16 +5025,19 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "dev": true }, "chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } }, "clone-deep": { "version": "4.0.1", @@ -5160,9 +5051,9 @@ } }, "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, "commander": { @@ -5174,7 +5065,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "convert-source-map": { @@ -5184,9 +5075,9 @@ "dev": true }, "core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "requires": { "browserslist": "^4.28.1" @@ -5204,12 +5095,10 @@ } }, "darkreader": { - "version": "4.9.120", - "resolved": "https://registry.npmjs.org/darkreader/-/darkreader-4.9.120.tgz", - "integrity": "sha512-BYjP9rsFzwtO2KDXfzqGZHBfo9LAp7azyRR4UIj0/n8uls0C0J08Rhh5g05BZU/CARvDmMf+LQXM9PoPxO1M4A==", + "version": "4.9.89", + "resolved": "https://registry.npmjs.org/darkreader/-/darkreader-4.9.89.tgz", + "integrity": "sha512-mO/HFu69+U1szlAfkhW+1P4IcSeNV9Su6JD3zTKFyg61b5GMWY70lkBimD4NEDpY6DJ4Ks9kFFpENeG/UcsKYw==", "requires": { - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0", "malevic": "0.20.2" } }, @@ -5223,17 +5112,17 @@ } }, "dompurify": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", - "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "requires": { "@types/trusted-types": "^2.0.7" } }, "electron-to-chromium": { - "version": "1.5.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", - "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "dev": true }, "emojis-list": { @@ -5243,9 +5132,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", - "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -5334,9 +5223,9 @@ "dev": true }, "fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", "dev": true }, "find-cache-dir": { @@ -5360,16 +5249,10 @@ "path-exists": "^4.0.0" } }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { @@ -5425,9 +5308,9 @@ } }, "import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, "requires": { "pkg-dir": "^4.2.0", @@ -5437,7 +5320,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -5483,7 +5366,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, "jest-worker": { @@ -5631,15 +5514,15 @@ "dev": true }, "node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -5708,9 +5591,9 @@ } }, "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "raw-loader": { @@ -5886,15 +5769,15 @@ "dev": true }, "tapable": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", - "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true }, "terser": { - "version": "5.46.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", - "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", @@ -5904,9 +5787,9 @@ } }, "terser-webpack-plugin": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", - "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.25", @@ -5916,9 +5799,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.3", @@ -5956,10 +5839,16 @@ } } }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, "undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true }, "unicode-canonical-property-names-ecmascript": { @@ -6020,9 +5909,9 @@ } }, "webpack": { - "version": "5.105.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", - "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.7", @@ -6031,11 +5920,11 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", + "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.20.0", + "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -6047,15 +5936,15 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.17", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", - "webpack-sources": "^3.3.4" + "webpack-sources": "^3.3.3" }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.3", @@ -6122,20 +6011,19 @@ } }, "webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", "dev": true, "requires": { "clone-deep": "^4.0.1", - "flat": "^5.0.2", "wildcard": "^2.0.0" } }, "webpack-sources": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", - "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true }, "which": { @@ -6148,15 +6036,15 @@ } }, "wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { diff --git a/package.json b/package.json index abe009fb17b49..b3f936009bee4 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "license": "MPL-2.0", "dependencies": { "@mozilla/readability": "^0.6.0", - "darkreader": "^4.9.120", - "dompurify": "^3.3.3", + "darkreader": "^4.9.89", + "dompurify": "^3.3.2", "page-metadata-parser": "1.1.4" }, "devDependencies": { diff --git a/test-fixtures/ci/slack-notification-payload-autofill-test.json b/test-fixtures/ci/slack-notification-payload-autofill-test.json deleted file mode 100644 index 4a7814ed2c189..0000000000000 --- a/test-fixtures/ci/slack-notification-payload-autofill-test.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "attachments": [ - { - "color": "${{ env.JOB_STATUS_COLOR }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GitHub Action :github: credential provider playwrite tests", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Status: ${{ env.JOB_STATUS }}\n Logs: <${{ env.JOB_LOG_URL }}|Build Logs>" - } - }, - { - "type": "divider" - }, - { - "type": "context", - "elements": [ - { - "type": "mrkdwn", - "text": ":testops-notify: created by " - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/test-fixtures/ci/slack-notification-payload-autofill.json b/test-fixtures/ci/slack-notification-payload-autofill.json deleted file mode 100644 index 429dec56e885a..0000000000000 --- a/test-fixtures/ci/slack-notification-payload-autofill.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "attachments": [ - { - "color": "${{ env.JOB_STATUS_COLOR }}", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "GitHub Action :github: credential provider script", - "emoji": true - } - }, - { - "type": "divider" - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Status: ${{ env.JOB_STATUS }}\n Logs: <${{ env.JOB_LOG_URL }}|Build Logs>" - } - }, - { - "type": "divider" - }, - { - "type": "context", - "elements": [ - { - "type": "mrkdwn", - "text": ":testops-notify: created by " - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/test-fixtures/ci/slack-notification-payload-remote-settings-fetch.json b/test-fixtures/ci/slack-notification-payload-generic.json similarity index 93% rename from test-fixtures/ci/slack-notification-payload-remote-settings-fetch.json rename to test-fixtures/ci/slack-notification-payload-generic.json index 2edc86b089c03..8618143a8716c 100644 --- a/test-fixtures/ci/slack-notification-payload-remote-settings-fetch.json +++ b/test-fixtures/ci/slack-notification-payload-generic.json @@ -7,7 +7,7 @@ "type": "header", "text": { "type": "plain_text", - "text": "GitHub Action :github: remote settings fetch", + "text": "GitHub Action :github: ${{ env.ACTION_NAME }}", "emoji": true } }, @@ -36,4 +36,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/test-fixtures/newest_kingfisher_tag.txt b/test-fixtures/newest_kingfisher_tag.txt new file mode 100644 index 0000000000000..eec6dacbd4823 --- /dev/null +++ b/test-fixtures/newest_kingfisher_tag.txt @@ -0,0 +1 @@ +8.8.1 diff --git a/test-fixtures/update-kingfisher-version.py b/test-fixtures/update-kingfisher-version.py new file mode 100644 index 0000000000000..87f0bd8b95887 --- /dev/null +++ b/test-fixtures/update-kingfisher-version.py @@ -0,0 +1,145 @@ +import json +import logging +import re +from github import Github + +# Constants +KINGFISHER_REPO = "onevcat/Kingfisher" + +BROWSERKIT_PACKAGE_SWIFT = "BrowserKit/Package.swift" +BROWSERKIT_SPM_PACKAGE = "BrowserKit/Package.resolved" +FIREFOX_SPM_PACKAGE = "firefox-ios/Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" +SAMPLE_APP_SPM_PACKAGE = "SampleComponentLibraryApp/SampleComponentLibraryApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" +FOCUS_SPM_PACKAGE = "focus-ios/Blockzilla.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" + +SPM_PACKAGES = [ + BROWSERKIT_SPM_PACKAGE, + FIREFOX_SPM_PACKAGE, + SAMPLE_APP_SPM_PACKAGE, + FOCUS_SPM_PACKAGE, +] + + +def _init_logging(): + logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + level=logging.INFO, + ) + + +def get_latest_kingfisher_version(): + """Fetch the latest release version and commit SHA from GitHub.""" + repo = Github().get_repo(KINGFISHER_REPO) + latest_tag = repo.get_tags()[0] + version = latest_tag.name + revision = latest_tag.commit.sha + return version, revision + + +def read_version_from_package_swift(filepath): + """Read the current pinned version from BrowserKit/Package.swift.""" + try: + with open(filepath) as f: + content = f.read() + match = re.search(r'exact:\s*"(\d+\.\d+\.\d+)"', content) + return match.group(1) if match else None + except FileNotFoundError as e: + logging.error(f"File not found: {e}") + return None + + +def read_version_from_resolved(filepath): + """Read the current version and revision for Kingfisher from a Package.resolved. + + Handles both: + - v3 format: uses "identity" (lowercase) and "location" keys + - v1 format: uses "package" (capitalized) and "repositoryURL" keys (Focus/Blockzilla) + """ + try: + with open(filepath) as f: + data = json.load(f) + + # v1 wraps pins under an "object" key + pins = data.get("object", data).get("pins", []) + for pin in pins: + identity = pin.get("identity", pin.get("package", "")) + if identity.lower() == "kingfisher": + state = pin["state"] + return state.get("version"), state.get("revision") + except (FileNotFoundError, json.JSONDecodeError) as e: + logging.error(f"Error reading {filepath}: {e}") + return None, None + + +def compare_versions(current, latest): + """Return True if latest is strictly newer than current.""" + def to_tuple(v): + return tuple(int(x) for x in v.split(".")) + return to_tuple(latest) > to_tuple(current) + + +def update_file(filepath, old_version, new_version, old_revision, new_revision): + """Replace the version and revision strings in a file.""" + try: + with open(filepath, "r") as f: + content = f.read() + if old_version and new_version: + content = content.replace(old_version, new_version) + if old_revision and new_revision: + content = content.replace(old_revision, new_revision) + with open(filepath, "w") as f: + f.write(content) + logging.info(f"Updated {filepath}") + except (FileNotFoundError, IOError) as e: + logging.error(f"Error updating {filepath}: {e}") + + +def main(): + """ + STEPS + 1. Fetch latest Kingfisher tag from GitHub + 2. Update BrowserKit/Package.swift if its pinned version is behind latest + 3. Update each Package.resolved independently if its pinned version is behind latest + 4. Write newest_kingfisher_tag.txt if any file was changed (signals the workflow to open a PR) + """ + _init_logging() + + latest_version, latest_revision = get_latest_kingfisher_version() + logging.info(f"Latest Kingfisher on GitHub: {latest_version} ({latest_revision})") + + any_updated = False + + # Update BrowserKit/Package.swift if behind + swift_version = read_version_from_package_swift(BROWSERKIT_PACKAGE_SWIFT) + logging.info(f"Current Kingfisher in Package.swift: {swift_version}") + if swift_version and compare_versions(swift_version, latest_version): + logging.info(f"Updating Package.swift: {swift_version} -> {latest_version}") + update_file(BROWSERKIT_PACKAGE_SWIFT, swift_version, latest_version, None, None) + any_updated = True + else: + logging.info("Package.swift is already up to date.") + + # Update each Package.resolved independently (v1 and v3 formats handled in read_version_from_resolved) + for resolved_file in SPM_PACKAGES: + file_version, file_revision = read_version_from_resolved(resolved_file) + logging.info(f"Current Kingfisher in {resolved_file}: {file_version}") + if file_version and file_revision and compare_versions(file_version, latest_version): + logging.info(f"Updating {resolved_file}: {file_version} -> {latest_version}") + update_file(resolved_file, file_version, latest_version, file_revision, latest_revision) + any_updated = True + elif file_version and file_revision: + logging.info(f"{resolved_file} is already up to date.") + else: + logging.warning(f"Kingfisher entry not found in {resolved_file}, skipping.") + + if not any_updated: + logging.info("All Kingfisher references are already up to date. Nothing to do.") + return + + # Write the new tag so the workflow can use it in the PR title/branch name + with open("test-fixtures/newest_kingfisher_tag.txt", "w") as f: + f.write(latest_version + "\n") + + +if __name__ == "__main__": + main() diff --git a/version.txt b/version.txt index 1423089a12fb6..83d7d33fb8cef 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -150.2 \ No newline at end of file +150.3 \ No newline at end of file