-
Notifications
You must be signed in to change notification settings - Fork 440
Conversation
@@ -129,11 +130,13 @@ class SettingsViewController: UIViewController { | |||
@objc private func rewardsSwitchValueChanged() { | |||
state.ledger.isEnabled = settingsView.rewardsToggleSection.toggleSwitch.isOn | |||
updateVisualStateOfSections(animated: true) | |||
NotificationCenter.default.post(name: .rewardsToggled, object: nil) |
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.
We have to reset NTP notifications when either rewards or ads are toggled
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.
You should use LedgerObserver.rewardsEnabledStateUpdated
to manage when rewards status is updated as its status is actually toggled as well in WalletViewController
when the user has previous disabled rewards and clicks "Enable rewards" from the panel
|
||
// MARK: - New tab page | ||
extension Strings { | ||
public struct NTP { |
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 experimented a bit here with better strings structure.
@@ -161,6 +177,9 @@ class FavoritesViewController: UIViewController, Themeable { | |||
$0.removeObserver(self, name: .topSitesConversion, object: nil) | |||
$0.removeObserver(self, name: .privacyModeChanged, object: nil) | |||
} | |||
|
|||
// Navigating away from NTP counts the current notification as showed. | |||
Preferences.NewTabPage.brandedImageShowed.value = true |
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's one architectural caveat to this solution.
When you have a tab with website open and another empty new tab, navigating from NTP to the first tab counts as tab dismissal and the popup is cleared.
No simple way to prevent that, would require a bigger refactor
small fix for bottom sheet view.
92cca40
to
5975a65
Compare
@@ -4,14 +4,14 @@ | |||
|
|||
import BraveShared | |||
|
|||
internal struct Strings { | |||
public struct Strings { |
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.
Reason why we made this internal
was because it matches the same symbol as in BraveShared
module and caused issues. Not sure if any issues will arise in the future due to this change
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 can replace it with extension so there's always one struct
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.
or add duplicate strings, I only took like two small string values from BR-UI
@@ -129,11 +130,13 @@ class SettingsViewController: UIViewController { | |||
@objc private func rewardsSwitchValueChanged() { | |||
state.ledger.isEnabled = settingsView.rewardsToggleSection.toggleSwitch.isOn | |||
updateVisualStateOfSections(animated: true) | |||
NotificationCenter.default.post(name: .rewardsToggled, object: nil) |
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.
You should use LedgerObserver.rewardsEnabledStateUpdated
to manage when rewards status is updated as its status is actually toggled as well in WalletViewController
when the user has previous disabled rewards and clicks "Enable rewards" from the panel
} | ||
|
||
@objc private func adsToggleValueChanged() { | ||
state.ads.isEnabled = settingsView.adsSection.toggleSwitch.isOn | ||
updateVisualStateOfSections(animated: true) | ||
NotificationCenter.default.post(name: .adsToggled, object: nil) |
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.
Same as above, but we don't have an AdsObserver
unfortunately. Not sure if you this matters, but ads is also toggled in some states of onboarding (ads region becoming available to user who already opted into rewards):
brave-ios/Client/Frontend/Browser/Onboarding/OnboardingAdsAvailableController.swift
Line 32 in 24c14e7
rewards?.ads.isEnabled = true |
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.
onboarding doesn't matter
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.
But I need to observe both ads and rewards state changes. So callback for rewards and notification for ads?
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 that should be good yeah
BraveShared/BraveStrings.swift
Outdated
extension Strings { | ||
public struct NTP { | ||
public static let getPaidToSeeThisImage = | ||
NSL("ntp.getPaidToSeeThisImage", |
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.
Will the script pick up NSL
? Can't remember if it needs to actually parse NSLocalizedString
@@ -150,6 +150,15 @@ extension BrowserViewController { | |||
} | |||
} | |||
} | |||
|
|||
// Note: at the moment the toggle value does not matter, the same code is run regardless of its state. | |||
@objc func rewardsSettingToggled() { |
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.
Why not just have the notification registered to 1 selector then if they are going to run the same code? Also how come we don't care about the value of the actual toggle here?
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 can do 1 selector.
We do not care about value because it just resets the NTP notification, and what notification is shown depends on few conditions, rewards state, ads state, image type. Does not need to be shown immediately.
|
||
func update() { | ||
// Update dark blur, the more of content view go away the less dark it is. | ||
backgroundOverlayView.alpha = (maxY - yPosition) / maxY |
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.
Should probably check maxY != 0
otherwise you can end up with NaN and an exception thrown when you set the frame of contentView
below
} | ||
|
||
private var initialDrawerYPosition: CGFloat { | ||
let popupY = (view.frame.height / 2) - (contentView.frame.height / 2) |
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.
May want to ceil
/floor
this value, may end up with half pixels
updateDrawerViewConstraints() | ||
} | ||
|
||
private func updateDrawerViewConstraints() { |
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.
Why not just override updateConstraints
and call setNeedsUpdateConstraints()
in traitCollectionDidChange ?
|
||
// MARK: - Animations | ||
|
||
@objc private func didRecognizePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { |
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.
Nit: Name is a bit misleading, it implies it is only called once when it is recognized and not continuously as the user pans.
if gestureRecognizer.state != .ended { return } | ||
|
||
let velocity = gestureRecognizer.velocity(in: contentView).y | ||
let landingYPosition = yPosition + velocity / 10 |
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.
Use projected end point algorithm:
// Distance travelled after decelerating to zero velocity at a constant rate (credit: a WWDC video)
func project(initialVelocity: Float, decelerationRate: Float) -> Float {
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate)
}
Where deceleration rate can be taken from UIScrollView.DecelerationRate.normal
You can see its usage already here:
brave-ios/BraveRewardsUI/Ads/AdsViewController.swift
Lines 174 to 208 in 24c14e7
func project(initialVelocity: CGFloat, decelerationRate: CGFloat) -> CGFloat { | |
return (initialVelocity / 1000.0) * decelerationRate / (1.0 - decelerationRate) | |
} | |
private var panState: CGPoint = .zero | |
@objc private func dismissPannedAdView(_ pan: UIPanGestureRecognizer) { | |
guard let adView = pan.view as? AdView else { return } | |
switch pan.state { | |
case .began: | |
panState = adView.center | |
// Make sure to stop the dismiss timer | |
dismissTimers[adView]?.invalidate() | |
case .changed: | |
adView.transform.ty = min(0, pan.translation(in: adView).y) | |
case .ended: | |
let velocity = pan.velocity(in: adView).y | |
let y = min(panState.y, panState.y + pan.translation(in: adView).y) | |
let projected = project(initialVelocity: velocity, decelerationRate: UIScrollView.DecelerationRate.normal.rawValue) | |
if y + projected < 0 { | |
guard let displayedAd = self.displayedAds[adView] else { return } | |
hide(adView: adView, velocity: velocity) | |
displayedAd.handler(displayedAd.ad, .dismissed) | |
break | |
} | |
fallthrough | |
case .cancelled: | |
// Re-setup timeout timer | |
setupTimeoutTimer(for: adView) | |
adView.layer.springAnimate(property: kPOPLayerTranslationY, key: "translation.y") { animation, _ in | |
animation.toValue = 0 | |
} | |
default: | |
break | |
} | |
} |
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.
thank you for roasting this class, all this logic was taken from firefox and then adjusted by me
extension Strings { | ||
/// "BAT" or "BAT Points" depending on the region | ||
public static var BAT: String { | ||
return Preferences.Rewards.isUsingBAP.value == true ? "BAP" : "BAT" |
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.
So I duplicated some strings to leave BR-UI strings locked as internal.
Not perfect, we might want to rework that at some point
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.
Yeah this is OK, but in the end we can probably merge them into 1 struct. Reason it didnt start that way was because BraveRewardsUI used to be in its own repo.
44061de
to
6249ba1
Compare
49cac92
to
c26f53b
Compare
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.
1 more small thing :)
let nextYPosition: CGFloat | ||
|
||
let bottomHalfOfChildView = view.frame.height - (contentView.frame.height / 2) | ||
let pannedPastHalfOfViewHeight = yPosition > bottomHalfOfChildView | ||
|
||
let closeByVelocity = projectedVelocity > contentView.frame.height |
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 value of projectedVelocity
is distance travelled, not specific endpoint. You'll have to add its current yPosition
to this value to compare it against where you want the end point to be (in this case the contentView's maxY)
c26f53b
to
2be02a6
Compare
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.
UI/Runtime review:
For overall transparent overlay sheet:
- The black seems a bit harsh, I get that it helps readability but have we considered using a blurred background instead?
Code wise:
- Claim my rewards button should execute on touch up instead of touch down
Design wise (assuming there was some design somewhere?)
- Can we align the title and close button?
- Can we decrease the font-size of the button (and maybe give it some weight), seems so much bigger than the copy
For get paid to turn on ads screen:
Code wise:
- Hide sponsored images just immediately sets the pref right? Wasn't supposed to open settings?
- Highlights on this button only highlights the image for some reason. It should probably be set up with
.system
button type so that it highlights everything uniformly
For get paid to turn on rewards:
- Learn more action should be executed on touch up, not touch down
- Same issue with highlighting as above except there's no image, so there's no feedback at all when you tap either button
For "you can get paid" screen:
- Button action should be executed on touch up, not touch down
- Same highlight issue
This overlay takes quite a bit of space, I assume this is fine?
The overlay on landscape phone is fine. I will try to add the blur view instead of plain black with alpha Will fix the code concerns Rest is just what design team proposed, I will pass this feedback to @jamesmudgett |
Add dark blur.
view.addSubview(vc.view) | ||
vc.view.snp.remakeConstraints { | ||
$0.right.top.left.equalToSuperview() | ||
$0.bottom.equalTo(view) |
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.
Why is this separate if . view
is the superview?
I think $0.edges.equalToSuperview()
should work fine.
|
||
class NTPNotificationView: UIStackView { | ||
|
||
private lazy var titleStackView = UIStackView().then { |
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.
These shouldn't need to be lazy vars, using let
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.
They are, because each of those views don't need to be added to the main view.
Some views have headers and buttons, some don't
let initialY = initialDrawerYPosition | ||
|
||
// Only move the view if dragged below initial level. | ||
if yPosition <= initialY { |
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.
This could be reduced to: (but up to you)
// Only move the view if dragged below initial level.
yPosition = max(yPosition, initialY)
// Dragged all way down, remove the view.
yPosition = min(yPosition, maxY)
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.
feels less readable to me, i'll skip
contentView.snp.remakeConstraints { | ||
if showAsPopup || (traitCollection.userInterfaceIdiom == .phone | ||
&& UIApplication.shared.statusBarOrientation.isLandscape) { | ||
$0.bottom.equalToSuperview() |
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.
Can combine:
$0.bottom.centerX.equalToSuperview()
if yPosition == initialDrawerYPosition { return } | ||
|
||
contentView.snp.remakeConstraints { | ||
if showAsPopup || (traitCollection.userInterfaceIdiom == .phone |
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.
Maybe create const for traitCollection.userInterfaceIdiom == .phone && UIApplication.shared.statusBarOrientation.isLandscape
let iPhoneLandscape = traitCollection.userInterfaceIdiom == .phone && UIApplication.shared.statusBarOrientation.isLandscape
if showAsPopup && iPhoneLandscape { }
Probably not needed in this simple situation though.
$0.width.equalTo(maxHorizontalWidth) | ||
} else { | ||
$0.leading.trailing.equalToSuperview() | ||
$0.bottom.equalToSuperview() |
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.
Can add bottom
to above line.
Summary of Changes
This pull request fixes issue #2151
Submitter Checklist:
NSLocalizableString()
Test Plan:
Screenshots:
Reviewer Checklist:
QA/(Yes|No)
release-notes/(include|exclude)
bug
/enhancement