-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a banner to ask user to validate their email #24172
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fec8da8
Add a banner to ask user to validate their email
crazytonyli a15629b
Address PR comments
crazytonyli 5090d68
Replace Spacer() with .frame
crazytonyli 8677474
Update verify email cell UI
crazytonyli 5510b26
Update variables
crazytonyli 97e9327
Update UI to be similar to the web
crazytonyli 0760a08
Merge branch 'trunk' into verify-email-address
crazytonyli b640cfb
Fix typos
crazytonyli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
186 changes: 186 additions & 0 deletions
186
WordPress/Classes/ViewRelated/Me/Me Main/VerifyEmailRow.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import Foundation | ||
import UIKit | ||
import SwiftUI | ||
import WordPressUI | ||
import Combine | ||
|
||
final class VerifyEmailRow: ImmuTableRow { | ||
static let cell = ImmuTableCell.class(VerifyEmailCell.self) | ||
let action: ImmuTableAction? = nil | ||
|
||
func configureCell(_ cell: UITableViewCell) { | ||
// Do nothing. | ||
} | ||
} | ||
|
||
final class VerifyEmailCell: UITableViewCell { | ||
private let hostingView: UIHostingView<VerifyEmailView> | ||
|
||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
hostingView = .init(view: .init()) | ||
super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
setupView() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
private func setupView() { | ||
selectionStyle = .none | ||
|
||
contentView.addSubview(hostingView) | ||
hostingView.pinEdges(to: contentView) | ||
} | ||
} | ||
|
||
private struct VerifyEmailView: View { | ||
@StateObject private var viewModel = VerifyEmailViewModel() | ||
|
||
var body: some View { | ||
VStack(alignment: .leading, spacing: 8) { | ||
HStack { | ||
Image(systemName: "envelope.circle.fill") | ||
Text(Strings.verifyEmailTitle) | ||
} | ||
.foregroundStyle(Color(uiColor: #colorLiteral(red: 0.8392476439, green: 0.2103677094, blue: 0.2182099223, alpha: 1))) | ||
.font(.subheadline.weight(.semibold)) | ||
|
||
Text(viewModel.state.message) | ||
.font(.callout) | ||
.foregroundStyle(.primary) | ||
|
||
Spacer() | ||
|
||
Button { | ||
viewModel.sendVerificationEmail() | ||
} label: { | ||
HStack { | ||
if viewModel.state.showsActivityIndicator { | ||
ProgressView() | ||
.progressViewStyle(CircularProgressViewStyle()) | ||
} | ||
|
||
Text(viewModel.state.buttonTitle) | ||
.font(.callout) | ||
} | ||
} | ||
.buttonStyle(.borderless) | ||
.disabled(!viewModel.state.isButtonEnabled) | ||
} | ||
.padding() | ||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) | ||
} | ||
} | ||
|
||
// This value is not an actual "timeout" value of the verification link. It's just an arbitrary value to prevent | ||
// users from sending links repeatedly. | ||
private let verificationLinkTimeout: TimeInterval = 300 | ||
|
||
@MainActor | ||
private class VerifyEmailViewModel: ObservableObject { | ||
enum State { | ||
case needsVerification | ||
case sending | ||
case sent(Date) | ||
case error(Error) | ||
|
||
var message: String { | ||
let email = try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext)?.email ?? "" | ||
|
||
switch self { | ||
case .needsVerification, .sending, .sent: | ||
if let email, !email.isEmpty { | ||
return String(format: Strings.verifyMessage, email, Strings.sendButton) | ||
} else { | ||
return String(format: Strings.verifyMessageNoEmail, Strings.sendButton) | ||
} | ||
case .error(let error): | ||
return error.localizedDescription | ||
} | ||
} | ||
|
||
var buttonTitle: String { | ||
switch self { | ||
case .needsVerification: | ||
return Strings.sendButton | ||
case .sending: | ||
return Strings.sendingButton | ||
case .sent: | ||
return Strings.sentButton | ||
case .error: | ||
return Strings.retryButton | ||
} | ||
} | ||
|
||
var isButtonEnabled: Bool { | ||
switch self { | ||
case .needsVerification, .error: return true | ||
case .sending: return false | ||
case .sent(let date): | ||
return Date().timeIntervalSince(date) >= verificationLinkTimeout | ||
} | ||
} | ||
|
||
var showsActivityIndicator: Bool { | ||
if case .sending = self { | ||
return true | ||
} | ||
return false | ||
} | ||
} | ||
|
||
private let userID: NSNumber | ||
|
||
private var lastVerificationSentDate: Date? { | ||
get { | ||
let key = "LastEmailVerificationSentDate-\(userID)" | ||
return UserDefaults.standard.object(forKey: key) as? Date | ||
kean marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
set { | ||
let key = "LastEmailVerificationSentDate-\(userID)" | ||
UserDefaults.standard.set(newValue, forKey: key) | ||
} | ||
} | ||
|
||
@Published private(set) var state: State | ||
|
||
init() { | ||
userID = (try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext)?.userID) ?? 0 | ||
state = .needsVerification | ||
|
||
if let sentDate = lastVerificationSentDate, | ||
Date().timeIntervalSince(sentDate) < verificationLinkTimeout { | ||
state = .sent(sentDate) | ||
} | ||
} | ||
|
||
func sendVerificationEmail() { | ||
guard state.isButtonEnabled else { return } | ||
|
||
state = .sending | ||
|
||
let accountService = AccountService(coreDataStack: ContextManager.shared) | ||
accountService.requestVerificationEmail({ [weak self] in | ||
Task { @MainActor [weak self] in | ||
guard let self else { return } | ||
self.lastVerificationSentDate = Date() | ||
self.state = .sent(Date()) | ||
} | ||
}, failure: { [weak self] error in | ||
Task { @MainActor [weak self] in | ||
self?.state = .error(error) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
private enum Strings { | ||
static let verifyEmailTitle = NSLocalizedString("me.verifyEmail.title", value: "Verify Your Email", comment: "Title for email verification card") | ||
static let verifyMessage = NSLocalizedString("me.verifyEmail.message.withEmail", value: "Verify your email to secure your account and access more features.\nCheck your inbox at %@ for the confirmation email, or click '%@' to get a new one.", comment: "Message for email verification card with email address") | ||
static let verifyMessageNoEmail = NSLocalizedString("me.verifyEmail.message.noEmail", value: "Verify your email to secure your account and access more features.\nCheck your inbox for the confirmation email, or click '%@' to get a new one..", comment: "Message for email verification card") | ||
static let sendButton = NSLocalizedString("me.verifyEmail.button.send", value: "Resend email", comment: "Button title to send verification link") | ||
static let sendingButton = NSLocalizedString("me.verifyEmail.button.sending", value: "Sending...", comment: "Button title while verification link is being sent") | ||
static let sentButton = NSLocalizedString("me.verifyEmail.button.sent", value: "Email sent", comment: "Button title after verification link is sent") | ||
static let retryButton = NSLocalizedString("me.verifyEmail.button.retry", value: "Try Again", comment: "Button title when verification link sending failed") | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a bit on a high end. I'd go with 60 seconds, perhaps?
I'd suggest showing the timer in the UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think showing a countdown on the UI is not necessary? Showing a countdown feels like it's an action that the user needs to take action right now, but there is no real urgency in clicking the email link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app could start showing it if you tap "Resend" before it's possible. This way you'll know when you can tap it again. If that's what it already does – please ignore it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no real restrictions on the endpoint to limit when sending another email is possible.
BTW, I checked the website UI. It neither show a timer nor prevent users from keep resending emails. Once user refresh the webpage, they can send another one.
I pushed another commit to follow the web UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if it's relatively free, it's should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'll keep it the same with the web for now.