From 300a21e02afd6dbce992f7f9d52754124217a937 Mon Sep 17 00:00:00 2001 From: brave-builds Date: Tue, 25 Jun 2024 15:51:13 +0000 Subject: [PATCH] Uplift of #24325 (squashed) to beta --- .../Sources/AIChat/AIChatStrings.swift | 24 ++++++++ .../AIChat/Components/AIChatView.swift | 26 ++++++++- .../Errors/AIChatBusyErrorView.swift | 58 +++++++++++++++++++ .../AIChatSessionExpiredErrorView.swift | 58 +++++++++++++++++++ 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 ios/brave-ios/Sources/AIChat/Components/Errors/AIChatBusyErrorView.swift create mode 100644 ios/brave-ios/Sources/AIChat/Components/Errors/AIChatSessionExpiredErrorView.swift diff --git a/ios/brave-ios/Sources/AIChat/AIChatStrings.swift b/ios/brave-ios/Sources/AIChat/AIChatStrings.swift index e651e73db601..2281810aac3e 100644 --- a/ios/brave-ios/Sources/AIChat/AIChatStrings.swift +++ b/ios/brave-ios/Sources/AIChat/AIChatStrings.swift @@ -17,6 +17,15 @@ extension Strings { comment: "The title shown on limit reached error view, which is suggesting user to change default model" ) + public static let accountSessionExpiredDescription = NSLocalizedString( + "aichat.accountSessionExpiredDescription", + tableName: "BraveLeo", + bundle: .module, + value: + "Your Brave account session has expired. Please visit your account page to refresh, then come back to use premium features.", + comment: + "The description for the error message when the user's session has expired" + ) public static let newChatActionTitle = NSLocalizedString( "aichat.newChatActionTitle", tableName: "BraveLeo", @@ -24,6 +33,13 @@ extension Strings { value: "New Chat", comment: "The title for button that starts a new chat" ) + public static let refreshCredentialsActionTitle = NSLocalizedString( + "aichat.refreshCredentialsActionTitle", + tableName: "BraveLeo", + bundle: .module, + value: "Refresh", + comment: "The title for button that refreshes user credentials" + ) public static let networkErrorViewTitle = NSLocalizedString( "aichat.networkErrorViewTitle", tableName: "BraveLeo", @@ -39,6 +55,14 @@ extension Strings { value: "Retry", comment: "The title for button for re-try" ) + public static let busyErrorDescription = NSLocalizedString( + "aichat.busyErrorDescription", + tableName: "BraveLeo", + bundle: .module, + value: "Leo is too busy right now. Please try again in a few minutes.", + comment: + "The title for view that shows when leo is too busy (active disconnected) and the user is premium" + ) public static let feedbackSuccessAnswerLikedTitle = NSLocalizedString( "aichat.feedbackSuccessAnswerLikedTitle", tableName: "BraveLeo", diff --git a/ios/brave-ios/Sources/AIChat/Components/AIChatView.swift b/ios/brave-ios/Sources/AIChat/Components/AIChatView.swift index e623debaf4b7..7e9e67dd4f22 100644 --- a/ios/brave-ios/Sources/AIChat/Components/AIChatView.swift +++ b/ios/brave-ios/Sources/AIChat/Components/AIChatView.swift @@ -403,9 +403,31 @@ public struct AIChatView: View { @ViewBuilder private func apiErrorViews(for model: AIChatViewModel) -> some View { let isPremiumAccess = model.currentModel.access == .premium - let isPremium = model.premiumStatus == .active || model.premiumStatus == .activeDisconnected + let isPremium = model.premiumStatus == .active + let isPremiumDisconnected = model.premiumStatus == .activeDisconnected - if isPremiumAccess && !isPremium { + if isPremiumAccess && isPremiumDisconnected { + if let subscriptionState = BraveStoreSDK.shared.leoSubscriptionStatus?.state, + subscriptionState != .expired && subscriptionState != .revoked + { + // Purchased via AppStore + AIChatBusyErrorView { + Task { @MainActor in + if let orderId = Preferences.AIChat.subscriptionOrderId.value { + try? await BraveSkusSDK.shared.fetchCredentials(orderId: orderId, for: .leo) + } + + model.retryLastRequest() + } + } + } else { + // Purchased via Desktop + AIChatSessionExpiredErrorView { + openURL(URL.Brave.braveLeoRefreshCredentials) + dismiss() + } + } + } else if isPremiumAccess && !isPremium { AIChatPremiumUpsellView( upsellType: .premium, upgradeAction: { diff --git a/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatBusyErrorView.swift b/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatBusyErrorView.swift new file mode 100644 index 000000000000..e5dba65f059a --- /dev/null +++ b/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatBusyErrorView.swift @@ -0,0 +1,58 @@ +// 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 DesignSystem +import SwiftUI + +struct AIChatBusyErrorView: View { + var retryAction: () -> Void + + var body: some View { + HStack(alignment: .top, spacing: 0.0) { + Image(braveSystemName: "leo.warning.triangle-filled") + .foregroundStyle(Color(braveSystemName: .systemfeedbackWarningIcon)) + .padding([.bottom, .trailing]) + + VStack(alignment: .leading, spacing: 0.0) { + Text(Strings.AIChat.busyErrorDescription) + .font(.callout) + .foregroundColor(Color(braveSystemName: .systemfeedbackWarningText)) + .padding(.bottom) + + Button( + action: { + retryAction() + }, + label: { + Text(Strings.AIChat.retryActionTitle) + .font(.body.weight(.semibold)) + .foregroundColor(Color(.white)) + .padding() + .foregroundStyle(.white) + .background( + Color(braveSystemName: .buttonBackground), + in: Capsule() + ) + } + ) + .buttonStyle(.plain) + } + } + .padding() + .background(Color(braveSystemName: .systemfeedbackWarningBackground)) + .clipShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) + } +} + +#if DEBUG +struct AIChatBusyErrorView_Preview: PreviewProvider { + static var previews: some View { + AIChatBusyErrorView { + print("Retry") + } + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatSessionExpiredErrorView.swift b/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatSessionExpiredErrorView.swift new file mode 100644 index 000000000000..1bff233eb0dd --- /dev/null +++ b/ios/brave-ios/Sources/AIChat/Components/Errors/AIChatSessionExpiredErrorView.swift @@ -0,0 +1,58 @@ +// 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 DesignSystem +import SwiftUI + +struct AIChatSessionExpiredErrorView: View { + var refreshSession: () -> Void + + var body: some View { + HStack(alignment: .top, spacing: 0.0) { + Image(braveSystemName: "leo.warning.triangle-filled") + .foregroundStyle(Color(braveSystemName: .systemfeedbackWarningIcon)) + .padding([.bottom, .trailing]) + + VStack(alignment: .leading, spacing: 0.0) { + Text(Strings.AIChat.accountSessionExpiredDescription) + .font(.callout) + .foregroundColor(Color(braveSystemName: .systemfeedbackWarningText)) + .padding(.bottom) + + Button( + action: { + refreshSession() + }, + label: { + Text(Strings.AIChat.refreshCredentialsActionTitle) + .font(.body.weight(.semibold)) + .foregroundColor(Color(.white)) + .padding() + .foregroundStyle(.white) + .background( + Color(braveSystemName: .buttonBackground), + in: Capsule() + ) + } + ) + .buttonStyle(.plain) + } + } + .padding() + .background(Color(braveSystemName: .systemfeedbackWarningBackground)) + .clipShape(RoundedRectangle(cornerRadius: 8.0, style: .continuous)) + } +} + +#if DEBUG +struct AIChatSessionExpiredErrorView_Preview: PreviewProvider { + static var previews: some View { + AIChatSessionExpiredErrorView { + print("Refresh Credentials") + } + .previewLayout(.sizeThatFits) + } +} +#endif