diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Translate.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Translate.swift index 2eb515b7b3a7..f9e7336bedf9 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Translate.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Translate.swift @@ -4,9 +4,11 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. import BraveUI +import DesignSystem import Foundation import Onboarding import Preferences +import UIKit extension BrowserViewController: BraveTranslateScriptHandlerDelegate { func updateTranslateURLBar(tab: Tab?, state: TranslateURLBarButton.TranslateState) { @@ -50,28 +52,35 @@ extension BrowserViewController: BraveTranslateScriptHandlerDelegate { onContinueButtonPressed: { [weak self, weak tab] in guard let tab = tab, let self = self else { return } + self.topToolbar.locationView.translateButton.setOnboardingState(enabled: false) + if let scriptHandler = tab.getContentScript( name: BraveTranslateScriptHandler.scriptName ) as? BraveTranslateScriptHandler { - self.showTranslateOnboarding(tab: tab) { [weak scriptHandler] translateEnabled in - scriptHandler?.presentUI(on: self) - } + scriptHandler.presentUI(on: self) } }, onDisableFeature: { [weak self, weak tab] in guard let tab = tab, let self = self else { return } + self.topToolbar.locationView.translateButton.setOnboardingState(enabled: false) + Preferences.Translate.translateEnabled.value = false tab.translationState = .unavailable self.topToolbar.updateTranslateButtonState(.unavailable) } - ) + ), + autoLayoutConfiguration: .init(preferredWidth: self.view.bounds.width - (32.0 * 2.0)) ) + popover.arrowDistance = 10.0 + popover.previewForOrigin = .init( - view: self.topToolbar.locationView.translateButton, + view: self.topToolbar.locationView.translateButton.then { + $0.setOnboardingState(enabled: true) + }, action: { popover in popover.previewForOrigin = nil popover.dismissPopover() @@ -79,7 +88,8 @@ extension BrowserViewController: BraveTranslateScriptHandlerDelegate { } ) - popover.popoverDidDismiss = { _ in + popover.popoverDidDismiss = { [weak self] _ in + self?.topToolbar.locationView.translateButton.setOnboardingState(enabled: false) completion(Preferences.Translate.translateEnabled.value) } popover.present(from: self.topToolbar.locationView.translateButton, on: self) @@ -90,4 +100,16 @@ extension BrowserViewController: BraveTranslateScriptHandlerDelegate { completion(true) } } + + func presentToast(_ languageInfo: BraveTranslateLanguageInfo) { + let popover = PopoverController( + content: TranslateToast(languageInfo: languageInfo), + autoLayoutConfiguration: .phoneWidth + ) + + popover.popoverDidDismiss = { [weak self] _ in + + } + popover.present(from: self.topToolbar.locationView.translateButton, on: self) + } } diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/Toolbars/UrlBar/TranslateURLBarButton.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/Toolbars/UrlBar/TranslateURLBarButton.swift index 654e42fa7ae9..d78827c3e158 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/Toolbars/UrlBar/TranslateURLBarButton.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/Toolbars/UrlBar/TranslateURLBarButton.swift @@ -1,4 +1,4 @@ -// Copyright 2021 The Brave Authors. All rights reserved. +// Copyright 2024 The Brave Authors. All rights reserved. // 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 https://mozilla.org/MPL/2.0/. @@ -19,10 +19,14 @@ class TranslateURLBarButton: UIButton { } } + var imageIcon: UIImage? { + UIImage(braveSystemNamed: "leo.product.translate") + } + override init(frame: CGRect) { super.init(frame: frame) adjustsImageWhenHighlighted = false - setImage(UIImage(braveSystemNamed: "leo.product.translate"), for: .normal) + setImage(imageIcon, for: .normal) updateIconSize() } @@ -92,6 +96,42 @@ class TranslateURLBarButton: UIButton { } } + private lazy var gradientLayer = CAGradientLayer().then { + let gradient = BraveGradient( + stops: [ + .init(color: UIColor(rgb: 0xFA7250), position: 0.0), + .init(color: UIColor(rgb: 0xFF1893), position: 0.43), + .init(color: UIColor(rgb: 0xA78AFF), position: 1.0), + ], + angle: .figmaDegrees(314.42) + ) + + $0.frame = self.bounds + $0.type = gradient.type + $0.colors = gradient.stops.map(\.color.cgColor) + $0.locations = gradient.stops.map({ NSNumber(value: $0.position) }) + $0.startPoint = gradient.startPoint + $0.endPoint = gradient.endPoint + + let mask = CALayer() + mask.contents = imageIcon?.cgImage + mask.frame = $0.bounds + $0.mask = mask + } + + func setOnboardingState(enabled: Bool) { + if enabled { + gradientLayer.frame = imageView?.bounds ?? self.bounds + gradientLayer.mask?.frame = gradientLayer.bounds + + imageView?.layer.addSublayer(gradientLayer) + setImage(nil, for: .normal) + } else { + gradientLayer.removeFromSuperlayer() + setImage(imageIcon, for: .normal) + } + } + enum TranslateState { case available case unavailable diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/Translate/TranslateToast.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/Translate/TranslateToast.swift new file mode 100644 index 000000000000..ec914230c22b --- /dev/null +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/Translate/TranslateToast.swift @@ -0,0 +1,94 @@ +// Copyright 2024 The Brave Authors. All rights reserved. +// 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 https://mozilla.org/MPL/2.0/. + +import BraveUI +import SwiftUI + +struct TranslateToast: View { + + var languageInfo: BraveTranslateLanguageInfo + + var currentLanguageName: String { + if let languageCode = languageInfo.currentLanguage.languageCode?.identifier, + let languageName = Locale.current.localizedString(forLanguageCode: languageCode) + { + return languageName + } + return "Unknown Language" + } + + var pageLanguageName: String { + if let languageCode = languageInfo.pageLanguage?.languageCode?.identifier, + let languageName = Locale.current.localizedString(forLanguageCode: languageCode) + { + return languageName + } + return "Unknown Language" + } + + var body: some View { + HStack { + Image(braveSystemName: "leo.product.translate") + .symbolRenderingMode(.monochrome) + .foregroundStyle( + LinearGradient( + braveGradient: .init( + stops: [ + .init(color: UIColor(rgb: 0xFA7250), position: 0.0), + .init(color: UIColor(rgb: 0xFF1893), position: 0.43), + .init(color: UIColor(rgb: 0xA78AFF), position: 1.0), + ], + angle: .figmaDegrees(314.42) + ) + ) + ) + .padding(.trailing) + VStack { + Text("Page Translations") + .font(.callout.weight(.semibold)) + .foregroundColor(Color(braveSystemName: .textPrimary)) + + Text( + "\(pageLanguageName) To \(currentLanguageName)" + ) + .font(.callout) + .foregroundColor(Color(braveSystemName: .textSecondary)) + } + .padding(.trailing) + + Spacer() + + Button { + + } label: { + Image(braveSystemName: "leo.settings") + .foregroundStyle(Color(braveSystemName: .iconDefault)) + } + + Color(braveSystemName: .dividerSubtle) + .frame(width: 1.0) + .padding([.top, .bottom], 8.0) + .padding([.leading, .trailing]) + + Button { + + } label: { + Text("Undo") + .foregroundStyle(Color(braveSystemName: .textInteractive)) + } + } + .padding() + } +} + +extension TranslateToast: PopoverContentComponent { + public var popoverBackgroundColor: UIColor { + .braveBackground + } +} + +#Preview { + TranslateToast(languageInfo: .init()) +} diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/TranslateToast.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/TranslateToast.swift deleted file mode 100644 index c2e220d1893e..000000000000 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/TranslateToast.swift +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2024 The Brave Authors. All rights reserved. -// 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 https://mozilla.org/MPL/2.0/. - -import BraveUI -import Foundation -import SnapKit -import UIKit - -class TranslateToast: Toast { - private struct DesignUX { - static let maxToastWidth: CGFloat = BraveUX.baseDimensionValue - } - - private let toastShadowView = ToastShadowView() - private var panState: CGPoint = .zero - - init( - completion: ((_ buttonPressed: Bool) -> Void)? - ) { - super.init(frame: .zero) - - self.completionHandler = completion - clipsToBounds = false - - addSubview(createView()) - - toastView.backgroundColor = UIColor.braveBlurpleTint - toastView.layer.cornerRadius = 8.0 - toastView.layer.cornerCurve = .continuous - toastView.layer.masksToBounds = true - - toastView.snp.makeConstraints { - $0.leading.trailing.equalTo(self).inset(ButtonToastUX.toastPadding * 2.0) - $0.height.equalTo(self) - self.animationConstraint = $0.top.equalTo(self).offset(ButtonToastUX.toastHeight).constraint - } - - self.snp.makeConstraints { - $0.height.equalTo(ButtonToastUX.toastHeight) - } - - let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onSwipeToDismiss(_:))) - toastView.addGestureRecognizer(panGesture) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func createView() -> UIView { - let label = UILabel() - label.textAlignment = .center - label.textColor = .white - label.font = ButtonToastUX.toastLabelFont - label.text = "Tap Here To Translate" - label.lineBreakMode = .byWordWrapping - label.numberOfLines = 0 - - let horizontalStackView = UIStackView().then { - $0.alignment = .center - $0.spacing = ButtonToastUX.toastPadding - } - - horizontalStackView.addArrangedSubview(label) - // toastView.addSubview(toastShadowView) - toastView.addSubview(horizontalStackView) - - // toastShadowView.snp.makeConstraints { - // $0.edges.equalToSuperview() - // } - - label.snp.makeConstraints { make in - make.centerX.equalTo(toastView) - } - - horizontalStackView.snp.makeConstraints { make in - make.centerX.equalTo(toastView) - make.centerY.equalTo(toastView) - make.width.equalTo(toastView.snp.width).offset(-2 * ButtonToastUX.toastPadding) - } - - return toastView - } - - @objc override func handleTap(_ gestureRecognizer: UIGestureRecognizer) { - dismiss(false) - } - - override func showToast( - viewController: UIViewController? = nil, - delay: DispatchTimeInterval, - duration: DispatchTimeInterval?, - makeConstraints: @escaping (ConstraintMaker) -> Void, - completion: (() -> Void)? = nil - ) { - super.showToast( - viewController: viewController, - delay: delay, - duration: duration, - makeConstraints: makeConstraints - ) - } - - func dismiss(_ buttonPressed: Bool) { - self.dismiss(buttonPressed, animated: true) - } - - @objc - private func onSwipeToDismiss(_ recognizer: UIPanGestureRecognizer) { - // Distance travelled after decelerating to zero velocity at a constant rate - func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat { - return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate) - } - - if recognizer.state == .began { - panState = toastView.center - } else if recognizer.state == .ended { - let velocity = recognizer.velocity(in: toastView) - if abs(velocity.y) > abs(velocity.x) { - let y = min(panState.y, panState.y + recognizer.translation(in: toastView).y) - let projected = project( - initialVelocity: velocity.y, - decelerationRate: UIScrollView.DecelerationRate.normal.rawValue - ) - if y + projected > toastView.frame.maxY { - dismiss(false) - } - } - } - } -} diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Sandboxed/BraveTranslateScriptHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Sandboxed/BraveTranslateScriptHandler.swift index 43dde0d2809a..45b6ff47589a 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Sandboxed/BraveTranslateScriptHandler.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Sandboxed/BraveTranslateScriptHandler.swift @@ -15,6 +15,7 @@ import os.log protocol BraveTranslateScriptHandlerDelegate: NSObject { func updateTranslateURLBar(tab: Tab?, state: TranslateURLBarButton.TranslateState) func showTranslateOnboarding(tab: Tab?, completion: @escaping (_ translateEnabled: Bool) -> Void) + func presentToast(_ languageInfo: BraveTranslateLanguageInfo) } class BraveTranslateScriptLanguageDetectionHandler: NSObject, TabContentScript { @@ -275,6 +276,7 @@ class BraveTranslateScriptHandler: NSObject, TabContentScript { ) self.delegate?.updateTranslateURLBar(tab: self.tab, state: .active) + self.delegate?.presentToast(currentLanguageInfo) } } diff --git a/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/Contents.json b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Contents.json b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Contents.json new file mode 100644 index 000000000000..3c9144e597d9 --- /dev/null +++ b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Image@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Image@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@2x.png b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@2x.png new file mode 100644 index 000000000000..9f02c04b7369 Binary files /dev/null and b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@2x.png differ diff --git a/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@3x.png b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@3x.png new file mode 100644 index 000000000000..5c893ae7bc5a Binary files /dev/null and b/ios/brave-ios/Sources/Onboarding/Assets/Images.xcassets/Translate/translate-onboarding-icon.imageset/Image@3x.png differ diff --git a/ios/brave-ios/Sources/Onboarding/Callouts/OnboardingTranslateView.swift b/ios/brave-ios/Sources/Onboarding/Callouts/OnboardingTranslateView.swift index 3e2d3fb0c3d7..9e24190bae3b 100644 --- a/ios/brave-ios/Sources/Onboarding/Callouts/OnboardingTranslateView.swift +++ b/ios/brave-ios/Sources/Onboarding/Callouts/OnboardingTranslateView.swift @@ -23,27 +23,21 @@ public struct OnboardingTranslateView: View { public var body: some View { ScrollView(.vertical) { VStack(spacing: 0.0) { - Image(braveSystemName: "leo.product.translate") - .font(.title) - .foregroundStyle(Color(braveSystemName: .textPrimary)) - .padding(16.0) - .background( - ContainerRelativeShape() - .fill(Color.white) - ) - .containerShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) - .padding([.top, .bottom], 32.0) + Image( + uiImage: UIImage(named: "translate-onboarding-icon", in: .module, compatibleWith: nil)! + ) + .padding([.top, .bottom], 24.0) Text("Page Translations") .font(.callout.weight(.semibold)) - .foregroundColor(Color.white) + .foregroundColor(Color(braveSystemName: .textPrimary)) .padding(.bottom) Text( "Pages can be translated to languages supported by your iOS device. You may be required to configure languages if this is your first time using this feature." ) .font(.callout) - .foregroundColor(Color.white) + .foregroundColor(Color(braveSystemName: .textSecondary)) .padding(.bottom, 24.0) Button( @@ -59,7 +53,7 @@ public struct OnboardingTranslateView: View { .foregroundStyle(Color(braveSystemName: .schemesOnPrimary)) .background( ContainerRelativeShape() - .fill(Color(braveSystemName: .primary40)) + .fill(Color(braveSystemName: .buttonBackground)) ) .containerShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) } @@ -77,23 +71,18 @@ public struct OnboardingTranslateView: View { .font(.body.weight(.semibold)) .padding() .frame(maxWidth: .infinity) - .foregroundStyle(Color(braveSystemName: .schemesOnPrimary)) + .foregroundStyle(Color(braveSystemName: .textSecondary)) .background( ContainerRelativeShape() - .fill(Color(.braveBlurpleTint)) + .fill(Color(.clear)) ) .containerShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) } ) .buttonStyle(.plain) } + .padding(24.0) } - .padding() - .frame( - minWidth: 200, - maxWidth: BraveUX.baseDimensionValue - ) - .background(Color(.braveBlurpleTint)) .multilineTextAlignment(.center) .osAvailabilityModifiers { content in #if compiler(>=5.8) @@ -118,7 +107,7 @@ public struct OnboardingTranslateView: View { extension OnboardingTranslateView: PopoverContentComponent { public var popoverBackgroundColor: UIColor { - .braveBlurpleTint + .braveBackground } }