Skip to content

Commit ac5ecf2

Browse files
authored
[iOS] - Uplift Leo QA Iteration 4 - 1.65.x (#22811)
* [iOS] - Retry Leo Purchases & Add Logging (#22791) * Add Logging View for Leo Purchases. * Add logs everywhere which can be accessed via Console.app * Retry purchase receipt syncing up to 10 times if purchase transaction was verified. * Revert manual refreshing logic as it doesn't help. * Fix performance of rendering chat messages. Signed-off-by: Brandon <[email protected]> Signed-off-by: Brandon T <[email protected]> * [iOS] - Fix layout of WrappingHStack when text becomes multi-line (#22608) * Fix layout of WrappingHStack when text becomes multi-line. * Also fix spacing between items in the wrapping HStack on iOS 15 when line becomes multi-line. Signed-off-by: Brandon <[email protected]> Signed-off-by: Brandon T <[email protected]> * Throw error when payment processing fails. Signed-off-by: Brandon T <[email protected]> --------- Signed-off-by: Brandon <[email protected]> Signed-off-by: Brandon T <[email protected]>
1 parent 34e5a93 commit ac5ecf2

18 files changed

+249
-139
lines changed

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

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public struct AIChatView: View {
1616
@ObservedObject
1717
var model: AIChatViewModel
1818

19+
@ObservedObject
20+
var speechRecognizer: SpeechRecognizer
21+
1922
@Environment(\.dismiss)
2023
private var dismiss
2124

@@ -42,8 +45,13 @@ public struct AIChatView: View {
4245

4346
var openURL: ((URL) -> Void)
4447

45-
public init(model: AIChatViewModel, openURL: @escaping (URL) -> Void) {
48+
public init(
49+
model: AIChatViewModel,
50+
speechRecognizer: SpeechRecognizer,
51+
openURL: @escaping (URL) -> Void
52+
) {
4653
self.model = model
54+
self.speechRecognizer = speechRecognizer
4755
self.openURL = openURL
4856
}
4957

@@ -236,7 +244,7 @@ public struct AIChatView: View {
236244
}
237245

238246
if model.isAgreementAccepted || (!hasSeenIntro.value && !model.isAgreementAccepted) {
239-
AIChatPromptInputView { prompt in
247+
AIChatPromptInputView(speechRecognizer: speechRecognizer) { prompt in
240248
hasSeenIntro.value = true
241249
model.submitQuery(prompt)
242250
hideKeyboard()
@@ -253,7 +261,7 @@ public struct AIChatView: View {
253261
AIChatPaywallView(
254262
premiumUpgrageSuccessful: { _ in
255263
Task { @MainActor in
256-
await model.refreshPremiumStatusOrderCredentials()
264+
await model.refreshPremiumStatus()
257265
}
258266
})
259267
}
@@ -265,7 +273,7 @@ public struct AIChatView: View {
265273
}
266274
.onAppear {
267275
Task { @MainActor in
268-
await model.refreshPremiumStatusOrderCredentials()
276+
await model.refreshPremiumStatus()
269277
}
270278

271279
if let query = model.querySubmited {
@@ -412,19 +420,14 @@ public struct AIChatView: View {
412420
},
413421
dismissAction: {
414422
Task { @MainActor in
415-
// This is needed to try to mitigate a bug in SkusSDK
416-
// See: https://github.com/brave/brave-browser/issues/36824
417-
// Also see the comment on the function
418-
if model.premiumStatus == .active || model.premiumStatus == .activeDisconnected {
419-
await model.refreshPremiumStatusOrderCredentials()
420-
}
421-
}
423+
await model.refreshPremiumStatus()
422424

423-
if let basicModel = model.models.first(where: { $0.access == .basic }) {
424-
model.changeModel(modelKey: basicModel.key)
425-
model.retryLastRequest()
426-
} else {
427-
Logger.module.error("No basic models available")
425+
if let basicModel = model.models.first(where: { $0.access == .basic }) {
426+
model.changeModel(modelKey: basicModel.key)
427+
model.retryLastRequest()
428+
} else {
429+
Logger.module.error("No basic models available")
430+
}
428431
}
429432
}
430433
)
@@ -441,6 +444,7 @@ public struct AIChatView: View {
441444

442445
private var feedbackView: some View {
443446
AIChatFeedbackView(
447+
speechRecognizer: speechRecognizer,
444448
premiumStatus: model.premiumStatus,
445449
shouldShowPremiumAd: shouldShowFeedbackPremiumAd.value,
446450
onSubmit: { category, feedback in
@@ -537,6 +541,7 @@ struct AIChatView_Preview: PreviewProvider {
537541
.background(Color(braveSystemName: .containerBackground))
538542

539543
AIChatFeedbackView(
544+
speechRecognizer: SpeechRecognizer(),
540545
premiumStatus: .inactive,
541546
shouldShowPremiumAd: true,
542547
onSubmit: {
@@ -572,7 +577,7 @@ struct AIChatView_Preview: PreviewProvider {
572577
)
573578
.padding()
574579

575-
AIChatPromptInputView {
580+
AIChatPromptInputView(speechRecognizer: SpeechRecognizer()) {
576581
print("Prompt Submitted: \($0)")
577582
}
578583
}

ios/brave-ios/Sources/AIChat/Components/Feedback/AIChatFeedbackView.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ private struct AIChatDropdownView: View {
185185
}
186186

187187
private struct AIChatFeedbackInputView: View {
188-
private var speechRecognizer = SpeechRecognizer()
188+
private var speechRecognizer: SpeechRecognizer
189189

190190
@State
191191
private var isVoiceEntryPresented = false
@@ -196,7 +196,8 @@ private struct AIChatFeedbackInputView: View {
196196
@Binding
197197
var text: String
198198

199-
init(text: Binding<String>) {
199+
init(speechRecognizer: SpeechRecognizer, text: Binding<String>) {
200+
self.speechRecognizer = speechRecognizer
200201
_text = text
201202
}
202203

@@ -337,6 +338,7 @@ struct AIChatFeedbackView: View {
337338
@State
338339
private var feedbackText: String = ""
339340

341+
var speechRecognizer: SpeechRecognizer
340342
var premiumStatus: AiChat.PremiumStatus
341343

342344
var shouldShowPremiumAd: Bool
@@ -360,7 +362,7 @@ struct AIChatFeedbackView: View {
360362
.frame(maxWidth: .infinity, alignment: .leading)
361363
.padding([.horizontal, .top])
362364

363-
AIChatFeedbackInputView(text: $feedbackText)
365+
AIChatFeedbackInputView(speechRecognizer: speechRecognizer, text: $feedbackText)
364366
.padding([.horizontal, .bottom])
365367

366368
if premiumStatus != .active && premiumStatus != .activeDisconnected && shouldShowPremiumAd {
@@ -397,6 +399,7 @@ struct AIChatFeedbackView: View {
397399
struct AIChatFeedbackView_Previews: PreviewProvider {
398400
static var previews: some View {
399401
AIChatFeedbackView(
402+
speechRecognizer: SpeechRecognizer(),
400403
premiumStatus: .inactive,
401404
shouldShowPremiumAd: true,
402405
onSubmit: {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SpeechRecognition
99
import SwiftUI
1010

1111
struct AIChatPromptInputView: View {
12-
private var speechRecognizer = SpeechRecognizer()
12+
private var speechRecognizer: SpeechRecognizer
1313

1414
@State
1515
private var isVoiceEntryPresented = false
@@ -22,7 +22,8 @@ struct AIChatPromptInputView: View {
2222

2323
var onSubmit: (String) -> Void
2424

25-
init(onSubmit: @escaping (String) -> Void) {
25+
init(speechRecognizer: SpeechRecognizer, onSubmit: @escaping (String) -> Void) {
26+
self.speechRecognizer = speechRecognizer
2627
self.onSubmit = onSubmit
2728
}
2829

@@ -109,7 +110,7 @@ struct AIChatPromptInputView: View {
109110
#if DEBUG
110111
struct AIChatPromptInputView_Preview: PreviewProvider {
111112
static var previews: some View {
112-
AIChatPromptInputView {
113+
AIChatPromptInputView(speechRecognizer: SpeechRecognizer()) {
113114
print("Prompt Submitted: \($0)")
114115
}
115116
.previewLayout(.sizeThatFits)

ios/brave-ios/Sources/AIChat/Components/Messages/AIChatSuggestionsView.swift

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ struct WrappingHStack: Layout {
5252
}
5353

5454
var x = bounds.minX
55-
var y = height / 2.0 + bounds.minY
55+
var y = height + bounds.minY
5656

5757
subviews.forEach { subview in
5858
x += subview.dimensions(in: proposal).width / 2.0
5959

6060
if x + subview.dimensions(in: proposal).width / 2.0 > bounds.maxX {
6161
x = bounds.minX + subview.dimensions(in: proposal).width / 2.0
62-
y += height + vSpacing
62+
y += subview.dimensions(in: proposal).height + vSpacing
6363
}
6464

6565
subview.place(
6666
at: CGPoint(x: x, y: y),
67-
anchor: .center,
67+
anchor: .init(x: 0.5, y: 1.0),
6868
proposal: ProposedViewSize(
6969
width: subview.dimensions(in: proposal).width,
7070
height: subview.dimensions(in: proposal).height
@@ -124,25 +124,23 @@ struct WrappingHStackOld<Model, V>: View where Model: Hashable, V: View {
124124
ZStack(alignment: .topLeading) {
125125
ForEach(models, id: \.self) { model in
126126
viewGenerator(model)
127-
.padding(.horizontal, hSpacing)
128-
.padding(.vertical, vSpacing)
129127
.alignmentGuide(
130128
.leading,
131129
computeValue: { dimension in
132130
if abs(width - dimension.width) > geometry.size.width {
133131
width = 0.0
134-
height -= dimension.height
132+
height -= dimension.height + vSpacing
135133
}
136134

137135
let result = width
138-
width = model == models.last! ? 0.0 : width - dimension.width
136+
width = model == models.last! ? 0.0 : width - dimension.width - hSpacing
139137
return result
140138
}
141139
)
142140
.alignmentGuide(
143141
.top,
144142
computeValue: { dimension in
145-
let result = height
143+
let result = height + dimension.height
146144
if model == models.last! {
147145
height = 0.0
148146
}

ios/brave-ios/Sources/AIChat/Components/Paywall/AIChatPaywallView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import DesignSystem
99
import StoreKit
1010
import SwiftUI
1111
import Then
12+
import os.log
1213

1314
enum AIChatPaymentStatus {
1415
case ongoing
@@ -231,6 +232,8 @@ struct AIChatPaywallView: View {
231232
paymentStatus = .success
232233
shouldDismiss = true
233234
} catch {
235+
Logger.module.debug("[AIChatPaywallView] - Purchase Failed: \(error)")
236+
234237
paymentStatus = .failure
235238
isShowingPurchaseAlert = true
236239
}

ios/brave-ios/Sources/AIChat/Components/Settings/AIChatAdvancedSettingsView.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public struct AIChatAdvancedSettingsView: View {
5757
.navigationViewStyle(.stack)
5858
.onAppear {
5959
Task { @MainActor in
60-
await model.refreshPremiumStatusOrderCredentials()
61-
await viewModel.fetchOrder()
60+
await model.refreshPremiumStatus()
61+
await viewModel.fetchCredentialSummary()
6262
}
6363
}
6464
} else {
@@ -67,8 +67,8 @@ public struct AIChatAdvancedSettingsView: View {
6767
.navigationBarTitleDisplayMode(.inline)
6868
.onAppear {
6969
Task { @MainActor in
70-
await model.refreshPremiumStatusOrderCredentials()
71-
await viewModel.fetchOrder()
70+
await model.refreshPremiumStatus()
71+
await viewModel.fetchCredentialSummary()
7272
}
7373
}
7474
}
@@ -309,8 +309,8 @@ public struct AIChatAdvancedSettingsView: View {
309309
AIChatPaywallView(
310310
premiumUpgrageSuccessful: { _ in
311311
Task { @MainActor in
312-
await model.refreshPremiumStatusOrderCredentials()
313-
await viewModel.fetchOrder()
312+
await model.refreshPremiumStatus()
313+
await viewModel.fetchCredentialSummary()
314314
}
315315
})
316316
}

ios/brave-ios/Sources/AIChat/Components/Settings/AIChatDefaultModelView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ struct AIChatDefaultModelView: View {
9393
AIChatPaywallView(
9494
premiumUpgrageSuccessful: { _ in
9595
Task { @MainActor in
96-
await aiModel.refreshPremiumStatusOrderCredentials()
96+
await aiModel.refreshPremiumStatus()
9797
}
9898
})
9999
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 BraveCore
7+
import BraveUI
8+
import OSLog
9+
import Preferences
10+
import SwiftUI
11+
import os.log
12+
13+
public struct AIChatLeoPurchaseLogs: View {
14+
@State
15+
private var text = ""
16+
17+
public init() {
18+
19+
}
20+
21+
public var body: some View {
22+
ScrollView {
23+
Text(text)
24+
.font(.caption)
25+
.fixedSize(horizontal: false, vertical: true)
26+
.frame(maxWidth: .infinity)
27+
.padding()
28+
}
29+
.task {
30+
repeat {
31+
text = await fetchLogs()
32+
try? await Task.sleep(seconds: 1.0)
33+
} while !Task.isCancelled
34+
}
35+
.navigationTitle("Leo Purchase Logs")
36+
.toolbar {
37+
Button("Copy") {
38+
UIPasteboard.general.setValue(text, forPasteboardType: "public.plain-text")
39+
}
40+
}
41+
}
42+
43+
private func fetchLogs() async -> String {
44+
do {
45+
let formatter = DateFormatter()
46+
formatter.dateStyle = .short
47+
formatter.timeStyle = .short
48+
49+
let store = try OSLogStore(scope: .currentProcessIdentifier)
50+
let logs = try store.getEntries()
51+
.compactMap { $0 as? OSLogEntryLog }
52+
.filter { $0.category == "AIChat" && $0.subsystem == Bundle.main.bundleIdentifier }
53+
.map { "\(formatter.string(from: $0.date)): \($0.composedMessage)" }
54+
.joined(separator: "\n----------------------\n")
55+
56+
return logs + "\n\n ---- Skus Info ----\n\n" + (await getSkusState())
57+
} catch {
58+
return "Error Fetching Logs: \(error)"
59+
}
60+
}
61+
62+
private func getSkusState() async -> String {
63+
var result = ""
64+
let orderId = Preferences.AIChat.subscriptionOrderId.value ?? "None"
65+
66+
result += "OrderId: \(orderId)\n"
67+
68+
do {
69+
let credentials = try await BraveSkusSDK.shared.credentialsSummary(for: .leo)
70+
result += "Credentials: \(credentials)\n"
71+
} catch {
72+
result += "Credentials Error: \(error)\n"
73+
}
74+
75+
return result
76+
}
77+
}

ios/brave-ios/Sources/AIChat/Components/Settings/AIChatSubscriptionDetailModelView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class AIChatSubscriptionDetailModelView: ObservableObject {
5252
}
5353

5454
@MainActor
55-
func fetchOrder() async {
55+
func fetchCredentialSummary() async {
5656
self.isLoading = true
5757

5858
if storeSDK.leoSubscriptionStatus != nil {

0 commit comments

Comments
 (0)