Skip to content

Commit fb7441b

Browse files
author
Giorgio Ruscigno
committed
merge branch trunk into task/19570-migration-notifications-screen-ui
2 parents 157e3a6 + 1445653 commit fb7441b

File tree

29 files changed

+437
-55
lines changed

29 files changed

+437
-55
lines changed

RELEASE-NOTES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
21.3
2+
-----
3+
* [*] Fixed a minor UI issue where the segmented control under My SIte was being clipped when "Home" is selected. [#19595]
4+
15
21.2
26
-----
37
* [*] [internal] Refactored fetching posts in the Reader tab. [#19539]

WordPress/Classes/Extensions/UIImageView+SiteIcon.swift

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,14 @@ extension UIImageView {
1111
/// Default Settings
1212
///
1313
struct SiteIconDefaults {
14-
1514
/// Default SiteIcon's Image Size, in points.
1615
///
17-
static let imageSize = 40
18-
19-
/// Default SiteIcon's Image Size, in pixels.
20-
///
21-
static var imageSizeInPixels: Int {
22-
return imageSize * Int(UIScreen.main.scale)
23-
}
16+
static let imageSize = CGSize(width: 40, height: 40)
2417
}
2518

2619

2720
/// Downloads the SiteIcon Image, hosted at the specified path. This method will attempt to optimize the URL, so that
28-
/// the download Image Size matches `SiteIconDefaults.imageSize`.
21+
/// the download Image Size matches `imageSize`.
2922
///
3023
/// TODO: This is a convenience method. Nuke me once we're all swifted.
3124
///
@@ -38,35 +31,42 @@ extension UIImageView {
3831

3932

4033
/// Downloads the SiteIcon Image, hosted at the specified path. This method will attempt to optimize the URL, so that
41-
/// the download Image Size matches `SiteIconDefaults.imageSize`.
34+
/// the download Image Size matches `imageSize`.
4235
///
4336
/// - Parameters:
4437
/// - path: SiteIcon's url (string encoded) to be downloaded.
38+
/// - imageSize: Request site icon in the specified image size.
4539
/// - placeholderImage: Yes. It's the "place holder image", Sherlock.
4640
///
4741
@objc
48-
func downloadSiteIcon(at path: String, placeholderImage: UIImage?) {
49-
guard let siteIconURL = optimizedURL(for: path) else {
42+
func downloadSiteIcon(
43+
at path: String,
44+
imageSize: CGSize = SiteIconDefaults.imageSize,
45+
placeholderImage: UIImage?
46+
) {
47+
guard let siteIconURL = optimizedURL(for: path, imageSize: imageSize) else {
5048
image = placeholderImage
5149
return
5250
}
5351

5452
logURLOptimization(from: path, to: siteIconURL)
5553

5654
let request = URLRequest(url: siteIconURL)
57-
downloadSiteIcon(with: request, placeholderImage: placeholderImage)
55+
downloadSiteIcon(with: request, imageSize: imageSize, placeholderImage: placeholderImage)
5856
}
5957

6058
/// Downloads a SiteIcon image, using a specified request.
6159
///
6260
/// - Parameters:
63-
/// - request: the request for the SiteIcon.
61+
/// - request: The request for the SiteIcon.
62+
/// - imageSize: Request site icon in the specified image size.
6463
/// - placeholderImage: Yes. It's the "place holder image".
6564
///
6665
private func downloadSiteIcon(
6766
with request: URLRequest,
68-
placeholderImage: UIImage?) {
69-
67+
imageSize expectedSize: CGSize = SiteIconDefaults.imageSize,
68+
placeholderImage: UIImage?
69+
) {
7070
af_setImage(withURLRequest: request, placeholderImage: placeholderImage, completion: { [weak self] dataResponse in
7171
switch dataResponse.result {
7272
case .success(let image):
@@ -82,8 +82,6 @@ extension UIImageView {
8282
// The following lines of code ensure that we resize the image to the default Site Icon size, to
8383
// ensure there is no UI breakage due to having larger images set here.
8484
//
85-
let expectedSize = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize)
86-
8785
if image.size != expectedSize {
8886
self.image = image.resizedImage(with: .scaleAspectFill, bounds: expectedSize, interpolationQuality: .default)
8987
} else {
@@ -103,19 +101,21 @@ extension UIImageView {
103101

104102

105103
/// Downloads the SiteIcon Image, associated to a given Blog. This method will attempt to optimize the URL, so that
106-
/// the download Image Size matches `SiteIconDefaults.imageSize`.
104+
/// the download Image Size matches `imageSize`.
107105
///
108106
/// - Parameters:
109107
/// - blog: reference to the source blog
110108
/// - placeholderImage: Yes. It's the "place holder image".
111109
///
112-
@objc
113-
func downloadSiteIcon(for blog: Blog, placeholderImage: UIImage? = .siteIconPlaceholder) {
114-
guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath) else {
110+
@objc func downloadSiteIcon(
111+
for blog: Blog,
112+
imageSize: CGSize = SiteIconDefaults.imageSize,
113+
placeholderImage: UIImage? = .siteIconPlaceholder
114+
) {
115+
guard let siteIconPath = blog.icon, let siteIconURL = optimizedURL(for: siteIconPath, imageSize: imageSize) else {
115116

116117
if blog.isWPForTeams() && placeholderImage == .siteIconPlaceholder {
117-
let standardSize = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize)
118-
image = UIImage.gridicon(.p2, size: standardSize)
118+
image = UIImage.gridicon(.p2, size: imageSize)
119119
return
120120
}
121121

@@ -135,7 +135,7 @@ extension UIImageView {
135135
for: siteIconURL,
136136
from: host,
137137
onComplete: { [weak self] request in
138-
self?.downloadSiteIcon(with: request, placeholderImage: placeholderImage)
138+
self?.downloadSiteIcon(with: request, imageSize: imageSize, placeholderImage: placeholderImage)
139139
}) { error in
140140
DDLogError(error.localizedDescription)
141141
}
@@ -148,28 +148,28 @@ extension UIImageView {
148148
extension UIImageView {
149149
/// Returns the Size Optimized URL for a given Path.
150150
///
151-
func optimizedURL(for path: String) -> URL? {
151+
func optimizedURL(for path: String, imageSize: CGSize = SiteIconDefaults.imageSize) -> URL? {
152152
if isPhotonURL(path) || isDotcomURL(path) {
153-
return optimizedDotcomURL(from: path)
153+
return optimizedDotcomURL(from: path, imageSize: imageSize)
154154
}
155155

156156
if isBlavatarURL(path) {
157-
return optimizedBlavatarURL(from: path)
157+
return optimizedBlavatarURL(from: path, imageSize: imageSize)
158158
}
159159

160-
return optimizedPhotonURL(from: path)
160+
return optimizedPhotonURL(from: path, imageSize: imageSize)
161161
}
162162

163163

164164
// MARK: - Private Helpers
165165

166-
/// Returns the download URL for a square icon with a size of `SiteIconDefaults.imageSizeInPixels`
166+
/// Returns the download URL for a square icon with a size of `imageSize` in pixels.
167167
///
168168
/// - Parameter path: SiteIcon URL (string encoded).
169169
///
170-
private func optimizedDotcomURL(from path: String) -> URL? {
171-
let size = SiteIconDefaults.imageSizeInPixels
172-
let query = String(format: "w=%d&h=%d", size, size)
170+
private func optimizedDotcomURL(from path: String, imageSize: CGSize = SiteIconDefaults.imageSize) -> URL? {
171+
let size = imageSize.toPixels()
172+
let query = String(format: "w=%d&h=%d", Int(size.width), Int(size.height))
173173

174174
return parseURL(path: path, query: query)
175175
}
@@ -179,9 +179,9 @@ extension UIImageView {
179179
///
180180
/// - Parameter path: Blavatar URL (string encoded).
181181
///
182-
private func optimizedBlavatarURL(from path: String) -> URL? {
183-
let size = SiteIconDefaults.imageSizeInPixels
184-
let query = String(format: "d=404&s=%d", size)
182+
private func optimizedBlavatarURL(from path: String, imageSize: CGSize = SiteIconDefaults.imageSize) -> URL? {
183+
let size = imageSize.toPixels()
184+
let query = String(format: "d=404&s=%d", Int(max(size.width, size.height)))
185185

186186
return parseURL(path: path, query: query)
187187
}
@@ -191,13 +191,12 @@ extension UIImageView {
191191
///
192192
/// - Parameter siteIconPath: SiteIcon URL (string encoded).
193193
///
194-
private func optimizedPhotonURL(from path: String) -> URL? {
194+
private func optimizedPhotonURL(from path: String, imageSize: CGSize = SiteIconDefaults.imageSize) -> URL? {
195195
guard let url = URL(string: path) else {
196196
return nil
197197
}
198198

199-
let size = CGSize(width: SiteIconDefaults.imageSize, height: SiteIconDefaults.imageSize)
200-
return PhotonImageURLHelper.photonURL(with: size, forImageURL: url)
199+
return PhotonImageURLHelper.photonURL(with: imageSize, forImageURL: url)
201200
}
202201

203202

@@ -267,3 +266,19 @@ private extension UIImageView {
267266
DDLogInfo("URL optimized from \(original) to \(optimized.absoluteString) for blog \(blogInfo)")
268267
}
269268
}
269+
270+
// MARK: - CGFloat Extension
271+
272+
private extension CGSize {
273+
274+
func toPixels() -> CGSize {
275+
return CGSize(width: width.toPixels(), height: height.toPixels())
276+
}
277+
}
278+
279+
private extension CGFloat {
280+
281+
func toPixels() -> CGFloat {
282+
return self * UIScreen.main.scale
283+
}
284+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Foundation
2+
3+
/// The service is created to support disabling WordPress notifications when Jetpack app is installed
4+
/// The service uses App Groups which allows Jetpack app to change the state of notifications flag and be later accessed by WordPress app
5+
/// This is a temporary solution to avoid duplicate notifications during the migration process from WordPress to Jetpack app
6+
/// This service and its usage can be deleted once the migration is done
7+
final class NotificationFilteringService {
8+
private var notificationSettingsLoader: NotificationSettingsLoader
9+
private var notificationsEnabled: Bool = false
10+
private let allowDisablingWPNotifications: Bool
11+
private let isWordPress: Bool
12+
private let userDefaults = UserDefaults(suiteName: WPAppGroupName)
13+
14+
var wordPressNotificationsEnabled: Bool {
15+
get {
16+
guard let userDefaults = userDefaults,
17+
userDefaults.value(forKey: WPNotificationsEnabledKey) != nil else {
18+
/// Treat this flag as enabled if it wasn't explicitly disabled
19+
return true
20+
}
21+
22+
return userDefaults.bool(forKey: WPNotificationsEnabledKey)
23+
}
24+
25+
set {
26+
userDefaults?.set(newValue, forKey: WPNotificationsEnabledKey)
27+
}
28+
}
29+
30+
init(notificationSettingsLoader: NotificationSettingsLoader = UNUserNotificationCenter.current(),
31+
allowDisablingWPNotifications: Bool = FeatureFlag.allowDisablingWPNotifications.enabled,
32+
isWordPress: Bool = AppConfiguration.isWordPress) {
33+
self.notificationSettingsLoader = notificationSettingsLoader
34+
self.allowDisablingWPNotifications = allowDisablingWPNotifications
35+
self.isWordPress = isWordPress
36+
37+
notificationSettingsLoader.getNotificationAuthorizationStatus { [weak self] status in
38+
self?.notificationsEnabled = status == .authorized
39+
}
40+
}
41+
42+
func shouldShowNotificationControl() -> Bool {
43+
return allowDisablingWPNotifications && isWordPress && notificationsEnabled
44+
}
45+
46+
func disableWordPressNotificationsIfNeeded() {
47+
if allowDisablingWPNotifications, !isWordPress {
48+
wordPressNotificationsEnabled = false
49+
}
50+
}
51+
52+
func shouldFilterWordPressNotifications() -> Bool {
53+
return allowDisablingWPNotifications
54+
&& isWordPress
55+
&& !wordPressNotificationsEnabled
56+
}
57+
}
58+
59+
// MARK: - Helpers
60+
61+
protocol NotificationSettingsLoader: AnyObject {
62+
func getNotificationAuthorizationStatus(completionHandler: @escaping (UNAuthorizationStatus) -> Void)
63+
}
64+
65+
extension UNUserNotificationCenter: NotificationSettingsLoader {
66+
func getNotificationAuthorizationStatus(completionHandler: @escaping (UNAuthorizationStatus) -> Void) {
67+
getNotificationSettings { settings in
68+
completionHandler(settings.authorizationStatus)
69+
}
70+
}
71+
}

WordPress/Classes/System/Constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern NSString *const WPNotificationServiceExtensionKeychainServiceName;
4141
extern NSString *const WPNotificationServiceExtensionKeychainTokenKey;
4242
extern NSString *const WPNotificationServiceExtensionKeychainUsernameKey;
4343
extern NSString *const WPNotificationServiceExtensionKeychainUserIDKey;
44+
extern NSString *const WPNotificationsEnabledKey;
4445

4546
/// Share Extension Constants
4647
///

WordPress/Classes/System/Constants.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
NSString *const WPNotificationServiceExtensionKeychainTokenKey = @"OAuth2Token";
4848
NSString *const WPNotificationServiceExtensionKeychainUsernameKey = @"Username";
4949
NSString *const WPNotificationServiceExtensionKeychainUserIDKey = @"UserID";
50+
NSString *const WPNotificationsEnabledKey = @"WordPressNotificationsEnabled";
5051

5152
/// Share Extension Constants
5253
///

WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
3737
case newJetpackLandingScreen
3838
case newWordPressLandingScreen
3939
case newCoreDataContext
40+
case allowDisablingWPNotifications
4041
case jetpackFeaturesRemovalPhaseOne
4142
case jetpackFeaturesRemovalPhaseTwo
4243
case jetpackFeaturesRemovalPhaseThree
@@ -124,6 +125,8 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
124125
return false
125126
case .newCoreDataContext:
126127
return true
128+
case .allowDisablingWPNotifications:
129+
return false
127130
case .jetpackFeaturesRemovalPhaseOne:
128131
return false
129132
case .jetpackFeaturesRemovalPhaseTwo:
@@ -154,6 +157,8 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
154157
return "jp_removal_four"
155158
case .jetpackFeaturesRemovalPhaseNewUsers:
156159
return "jp_removal_new_users"
160+
case .allowDisablingWPNotifications:
161+
return "prevent_duplicate_notifs_remote_field"
157162
default:
158163
return nil
159164
}
@@ -244,6 +249,8 @@ extension FeatureFlag {
244249
return "New WordPress landing screen"
245250
case .newCoreDataContext:
246251
return "Use new Core Data context structure (Require app restart)"
252+
case .allowDisablingWPNotifications:
253+
return "Disable WordPress app notifications when Jetpack is installed"
247254
case .jetpackFeaturesRemovalPhaseOne:
248255
return "Jetpack Features Removal Phase One"
249256
case .jetpackFeaturesRemovalPhaseTwo:

WordPress/Classes/Utility/InteractiveNotificationsManager.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ final class InteractiveNotificationsManager: NSObject {
6060
let options: UNAuthorizationOptions = [.badge, .sound, .alert, .providesAppNotificationSettings]
6161

6262
let notificationCenter = UNUserNotificationCenter.current()
63-
notificationCenter.requestAuthorization(options: options) { (allowed, _) in
63+
notificationCenter.requestAuthorization(options: options) { [weak self] (allowed, _) in
6464
DispatchQueue.main.async {
6565
if allowed {
6666
WPAnalytics.track(.pushNotificationOSAlertAllowed)
67+
self?.disableWordPressNotificationsIfNeeded()
6768
} else {
6869
WPAnalytics.track(.pushNotificationOSAlertDenied)
6970
}
@@ -687,3 +688,12 @@ extension InteractiveNotificationsManager: UNUserNotificationCenterDelegate {
687688
MeNavigationAction.notificationSettings.perform(router: UniversalLinkRouter.shared)
688689
}
689690
}
691+
692+
private extension InteractiveNotificationsManager {
693+
/// A temporary setting to allow controlling WordPress notifications when they are disabled after Jetpack installation
694+
/// Disable WordPress notifications when they are enabled on Jetpack
695+
func disableWordPressNotificationsIfNeeded() {
696+
let notificationFilteringService = NotificationFilteringService()
697+
notificationFilteringService.disableWordPressNotificationsIfNeeded()
698+
}
699+
}

WordPress/Classes/ViewRelated/Blog/My Site/MySiteViewController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,7 @@ class MySiteViewController: UIViewController, NoResultsViewHost {
844844
blogDashboardViewController.update(blog: blog)
845845
embedChildInStackView(blogDashboardViewController)
846846
self.blogDashboardViewController = blogDashboardViewController
847+
stackView.sendSubviewToBack(blogDashboardViewController.view)
847848
}
848849

849850
// MARK: - Model Changes

0 commit comments

Comments
 (0)