Skip to content

Commit 67c9505

Browse files
authored
[iOS] - Uplift 1.63.x of Leo Bug Fixes QA Iteration 1 (#22519)
* Fix popover not displaying correctly when something else is presented (keyboard, other controller, etc). Update strings to be lowercased localized and uppercased in code. Allow QA to test production links on a release channel. Fix premium subscription info discrepancy. The AppStore says the user is subscribed, but Leo says they are not. We only allow the user to MANUALLY activate premium so this causes a bug. Fix yet another bug in Skus SDK where order does not refresh automatically Fix popover clipping on iPad Regenerate other chat indices instead of just the last one. Fix textField not being fully tappable. * Fix compiling on 1.63 and 1.64.x
1 parent 076fc06 commit 67c9505

14 files changed

+306
-94
lines changed

ios/brave-ios/Sources/AIChat/AIChatStrings.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ extension Strings {
4747
"aichat.feedbackSuccessAnswerDisLikedTitle",
4848
tableName: "BraveLeo",
4949
bundle: .module,
50-
value: "Answer DisLiked",
50+
value: "Answer Disliked",
5151
comment: "The title for feedback view when response is sucessfull but disliked"
5252
)
5353
public static let feedbackSubmittedTitle = NSLocalizedString(
@@ -495,7 +495,7 @@ extension Strings {
495495
"aichat.advancedSettingsSubscriptionHeaderTitle",
496496
tableName: "BraveLeo",
497497
bundle: .module,
498-
value: "SUBSCRIPTION",
498+
value: "Subscription",
499499
comment: "The title for the header for subscription details"
500500
)
501501
public static let appStoreErrorTitle = NSLocalizedString(
@@ -551,21 +551,21 @@ extension Strings {
551551
"wallet.defaultModelChatSectionTitle",
552552
tableName: "BraveWallet",
553553
bundle: .module,
554-
value: "CHAT",
554+
value: "Chat",
555555
comment: "The title of the section where chat models are displayed as a list."
556556
)
557557
public static let unlimitedModelStatusTitle = NSLocalizedString(
558558
"wallet.unlimitedModelStatusTitle",
559559
tableName: "BraveWallet",
560560
bundle: .module,
561-
value: "UNLIMITED",
561+
value: "Unlimited",
562562
comment: "The title of the badge where a model which can be used unlimited"
563563
)
564564
public static let limitedModelStatusTitle = NSLocalizedString(
565565
"wallet.limitedModelStatusTitle",
566566
tableName: "BraveWallet",
567567
bundle: .module,
568-
value: "LIMITED",
568+
value: "Limited",
569569
comment: "The title of the badge where a model which can be used limited"
570570
)
571571
public static let defaultModelLanguageSectionTitle = NSLocalizedString(
@@ -607,7 +607,7 @@ extension Strings {
607607
"aichat.premiumNavigationBarBadgeTitle",
608608
tableName: "BraveLeo",
609609
bundle: .module,
610-
value: "PREMIUM",
610+
value: "Premium",
611611
comment: "Title shown next to Leo in navigation bar when user has premium subcription"
612612
)
613613
public static let infoAboutPageContext = NSLocalizedString(
@@ -726,7 +726,7 @@ extension Strings {
726726
"aichat.chatMenuSectionTitle",
727727
tableName: "BraveLeo",
728728
bundle: .module,
729-
value: "CHAT",
729+
value: "Chat",
730730
comment: "The title for the chat section in the menu"
731731
)
732732
public static let askLeoSearchSuggestionTitle = NSLocalizedString(

ios/brave-ios/Sources/AIChat/Components/AIChatNavigationView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ struct AIChatNavigationView<Content>: View where Content: View {
6565
.padding(.vertical)
6666

6767
if premiumStatus == .active || premiumStatus == .activeDisconnected {
68-
Text(Strings.AIChat.premiumNavigationBarBadgeTitle)
68+
Text(Strings.AIChat.premiumNavigationBarBadgeTitle.uppercased())
6969
.font(.caption2.weight(.bold))
7070
.foregroundStyle(Color(braveSystemName: .blue50))
7171
.padding(.horizontal, 6.0)

ios/brave-ios/Sources/AIChat/Components/AIChatView.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public struct AIChatView: View {
299299
}
300300
} else {
301301
// The purchase was through the Brave Account Website
302-
if BraveStoreSDK.shared.enviroment != .production {
302+
if BraveStoreSDK.shared.environment != .production {
303303
openURL(.brave.braveLeoManageSubscriptionStaging)
304304
} else {
305305
openURL(.brave.braveLeoManageSubscriptionProd)
@@ -321,9 +321,14 @@ public struct AIChatView: View {
321321
title: Strings.AIChat.responseContextMenuRegenerateTitle,
322322
icon: Image(braveSystemName: "leo.refresh"),
323323
onSelected: {
324-
model.retryLastRequest()
325-
})
326-
324+
if turnIndex == model.conversationHistory.count - 1 {
325+
model.retryLastRequest()
326+
} else if let query = model.conversationHistory[safe: turnIndex - 1]?.text {
327+
model.submitQuery(query)
328+
}
329+
}
330+
)
331+
327332
AIChatResponseMessageViewContextMenuButton(
328333
title: Strings.AIChat.responseContextMenuCopyTitle,
329334
icon: Image(braveSystemName: "leo.copy"),
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2024 The Brave Authors. All rights reserved.
2+
// This Source Code Form is subject to the terms of the Mozilla Public
3+
// License, v. 2.0. If a copy of the MPL was not distributed with this
4+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
6+
import Foundation
7+
import SwiftUI
8+
9+
struct AIChatPaddedTextField: UIViewRepresentable {
10+
private var title: String
11+
12+
@Binding
13+
private var text: String
14+
15+
private var textColor: UIColor?
16+
17+
private var prompt: String?
18+
19+
private var promptColor: UIColor?
20+
21+
private var font: UIFont?
22+
23+
private var submitLabel: UIReturnKeyType
24+
25+
private var onSubmit: (() -> Void)?
26+
27+
private var insets: CGSize
28+
29+
init<S>(
30+
_ title: S,
31+
text: Binding<String>,
32+
textColor: UIColor? = nil,
33+
prompt: String? = nil,
34+
promptColor: UIColor? = nil,
35+
font: UIFont? = nil,
36+
submitLabel: UIReturnKeyType = .done,
37+
onSubmit: (() -> Void)? = nil,
38+
insets: CGSize = .zero
39+
)
40+
where S: StringProtocol {
41+
self.title = String(title)
42+
self._text = text
43+
self.textColor = textColor
44+
self.prompt = prompt
45+
self.promptColor = promptColor
46+
self.font = font
47+
self.submitLabel = submitLabel
48+
self.onSubmit = onSubmit
49+
self.insets = insets
50+
}
51+
52+
func makeUIView(context: Context) -> PaddedTextField {
53+
let view = PaddedTextField()
54+
55+
view.accessibilityLabel = title
56+
view.text = text
57+
view.textColor = textColor
58+
view.font = font
59+
view.returnKeyType = submitLabel
60+
view.xInset = insets.width
61+
view.yInset = insets.height
62+
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
63+
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
64+
view.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
65+
view.delegate = context.coordinator
66+
view.addTarget(
67+
context.coordinator,
68+
action: #selector(Coordinator.textFieldTextChanged(_:)),
69+
for: .editingChanged
70+
)
71+
72+
if let prompt = prompt, !prompt.isEmpty {
73+
if let promptColor = promptColor {
74+
let attributedPlaceHolder = NSMutableAttributedString(string: prompt)
75+
attributedPlaceHolder.addAttribute(
76+
.foregroundColor,
77+
value: promptColor,
78+
range: NSRange(location: 0, length: prompt.count)
79+
)
80+
view.attributedPlaceholder = attributedPlaceHolder
81+
} else {
82+
view.placeholder = prompt
83+
}
84+
}
85+
return view
86+
}
87+
88+
func updateUIView(_ view: PaddedTextField, context: Context) {
89+
view.text = text
90+
}
91+
92+
func makeCoordinator() -> Coordinator {
93+
return Coordinator(text: $text, onSubmit: onSubmit)
94+
}
95+
96+
class PaddedTextField: UITextField {
97+
var xInset: CGFloat = 0.0
98+
var yInset: CGFloat = 0.0
99+
100+
override func textRect(forBounds bounds: CGRect) -> CGRect {
101+
return super.textRect(forBounds: bounds.insetBy(dx: xInset, dy: yInset))
102+
}
103+
104+
override func editingRect(forBounds bounds: CGRect) -> CGRect {
105+
return textRect(forBounds: bounds)
106+
}
107+
108+
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
109+
return textRect(forBounds: bounds)
110+
}
111+
}
112+
113+
class Coordinator: NSObject, UITextFieldDelegate {
114+
@Binding
115+
private var text: String
116+
117+
private var onSubmit: (() -> Void)?
118+
119+
init(text: Binding<String>, onSubmit: (() -> Void)?) {
120+
self._text = text
121+
self.onSubmit = onSubmit
122+
super.init()
123+
}
124+
125+
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
126+
onSubmit?()
127+
return true
128+
}
129+
130+
@objc
131+
func textFieldTextChanged(_ textField: UITextField) {
132+
self.text = textField.text ?? ""
133+
}
134+
}
135+
}

ios/brave-ios/Sources/AIChat/Components/AIChatPromptInputView.swift renamed to ios/brave-ios/Sources/AIChat/Components/Input/AIChatPromptInputView.swift

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,48 @@
33
// License, v. 2.0. If a copy of the MPL was not distributed with this
44
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
55

6-
import SwiftUI
6+
import AVFoundation
77
import DesignSystem
88
import SpeechRecognition
9-
import AVFoundation
9+
import SwiftUI
1010

1111
struct AIChatPromptInputView: View {
1212
private var speechRecognizer = SpeechRecognizer()
13-
13+
1414
@State
1515
private var isVoiceEntryPresented = false
16-
16+
1717
@State
1818
private var isNoMicrophonePermissionPresented = false
19-
19+
2020
@State
2121
private var prompt: String = ""
22-
22+
2323
var onSubmit: (String) -> Void
24-
24+
2525
init(onSubmit: @escaping (String) -> Void) {
2626
self.onSubmit = onSubmit
2727
}
2828

2929
var body: some View {
3030
HStack(spacing: 0.0) {
31-
TextField(
31+
AIChatPaddedTextField(
3232
Strings.AIChat.promptPlaceHolderDescription,
3333
text: $prompt,
34-
prompt: Text(Strings.AIChat.promptPlaceHolderDescription)
35-
.font(.subheadline)
36-
.foregroundColor(Color(braveSystemName: .textTertiary))
34+
textColor: UIColor(braveSystemName: .textPrimary),
35+
prompt: Strings.AIChat.promptPlaceHolderDescription,
36+
promptColor: UIColor(braveSystemName: .textTertiary),
37+
font: .preferredFont(forTextStyle: .subheadline),
38+
submitLabel: .send,
39+
onSubmit: {
40+
if !prompt.isEmpty {
41+
onSubmit(prompt)
42+
prompt = ""
43+
}
44+
},
45+
insets: .init(width: 16.0, height: 16.0)
3746
)
38-
.font(.subheadline)
39-
.foregroundColor(Color(braveSystemName: .textPrimary))
40-
.submitLabel(.send)
41-
.onSubmit {
42-
if !prompt.isEmpty {
43-
onSubmit(prompt)
44-
prompt = ""
45-
}
46-
}
47-
.padding(.leading)
48-
47+
4948
if prompt.isEmpty {
5049
Button {
5150
Task { @MainActor in
@@ -63,6 +62,8 @@ struct AIChatPromptInputView: View {
6362
.labelStyle(.iconOnly)
6463
}
6564
.opacity(speechRecognizer.isVoiceSearchAvailable ? 1.0 : 0.0)
65+
.disabled(!speechRecognizer.isVoiceSearchAvailable)
66+
.frame(width: speechRecognizer.isVoiceSearchAvailable ? nil : 0.0)
6667
} else {
6768
Button {
6869
onSubmit(prompt)
@@ -93,7 +94,7 @@ struct AIChatPromptInputView: View {
9394
)
9495
}
9596
}
96-
97+
9798
@MainActor
9899
private func activateSpeechRecognition() async {
99100
let permissionStatus = await speechRecognizer.askForUserPermission()
@@ -108,7 +109,7 @@ struct AIChatPromptInputView: View {
108109
#if DEBUG
109110
struct AIChatPromptInputView_Preview: PreviewProvider {
110111
static var previews: some View {
111-
AIChatPromptInputView() {
112+
AIChatPromptInputView {
112113
print("Prompt Submitted: \($0)")
113114
}
114115
.previewLayout(.sizeThatFits)

ios/brave-ios/Sources/AIChat/Components/PageContext/AIChatPageContextView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct AIChatPageContextView: View {
4949
) {
5050
AIChatPageContextInfoView(url: url, pageTitle: pageTitle)
5151
.background(Color(braveSystemName: .containerBackground))
52+
.frame(maxWidth: 400.0)
5253
}
5354
}
5455
}

ios/brave-ios/Sources/AIChat/Components/Popover/PopoverView.swift

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,43 @@ struct BravePopoverView<Content: View & PopoverContentComponent>: UIViewControll
5353

5454
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
5555
if isPresented {
56-
guard uiViewController.presentedViewController == nil, let parent = uiViewController.parent else {
56+
guard uiViewController.presentedViewController == nil
57+
else {
58+
// The system dismissed our popover automatically, but never updated our presentation state
59+
// It usually does this if you present another popover or sheet
60+
// Manually update it
61+
if context.coordinator.presentedViewController != nil {
62+
DispatchQueue.main.async {
63+
isPresented = false
64+
}
65+
}
5766
return
5867
}
59-
60-
let controller = PopoverController(content: content)
61-
context.coordinator.presentedViewController = .init(controller)
62-
controller.popoverDidDismiss = { _ in
63-
self.isPresented = false
68+
69+
if let parent = uiViewController.parent, !parent.isBeingDismissed {
70+
let controller = PopoverController(content: content)
71+
context.coordinator.presentedViewController = .init(controller)
72+
controller.popoverDidDismiss = { _ in
73+
self.isPresented = false
74+
}
75+
76+
DispatchQueue.main.async {
77+
if KeyboardHelper.defaultHelper.currentState != nil {
78+
UIApplication.shared.sendAction(
79+
#selector(UIResponder.resignFirstResponder),
80+
to: nil,
81+
from: nil,
82+
for: nil
83+
)
84+
85+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
86+
controller.present(from: uiViewController.view, on: parent)
87+
}
88+
} else {
89+
controller.present(from: uiViewController.view, on: parent)
90+
}
91+
}
6492
}
65-
66-
controller.present(from: uiViewController.view, on: parent)
6793
} else {
6894
if let presentedViewController = context.coordinator.presentedViewController?.value,
6995
presentedViewController == uiViewController.presentedViewController {

0 commit comments

Comments
 (0)