Skip to content

Commit 89c9502

Browse files
Merge branch 'task/19447-phase-1' into task/19447-analytics
2 parents 8d49c23 + 543d760 commit 89c9502

File tree

159 files changed

+3132
-1866
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

159 files changed

+3132
-1866
lines changed

Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ abstract_target 'Apps' do
218218

219219
pod 'NSURL+IDN', '~> 0.4'
220220

221-
pod 'WPMediaPicker', '~> 1.8.4'
221+
pod 'WPMediaPicker', '~> 1.8.7-beta.1'
222222
# pod 'WPMediaPicker', :git => 'https://github.com/wordpress-mobile/MediaPicker-iOS.git', :tag => '1.7.0'
223223
## while PR is in review:
224224
# pod 'WPMediaPicker', :git => 'https://github.com/wordpress-mobile/MediaPicker-iOS.git', :branch => ''

Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ PODS:
512512
- CocoaLumberjack (~> 3.4)
513513
- FormatterKit/TimeIntervalFormatter (~> 1.8)
514514
- WordPressUI (1.12.5)
515-
- WPMediaPicker (1.8.6)
515+
- WPMediaPicker (1.8.7-beta.1)
516516
- wpxmlrpc (0.9.0)
517517
- Yoga (1.14.0)
518518
- ZendeskCommonUISDK (6.1.2)
@@ -605,7 +605,7 @@ DEPENDENCIES:
605605
- WordPressKit (>= 4.58.2, ~> 4.58)
606606
- WordPressShared (~> 1.18.0)
607607
- WordPressUI (~> 1.12.5)
608-
- WPMediaPicker (~> 1.8.4)
608+
- WPMediaPicker (~> 1.8.7-beta.1)
609609
- Yoga (from `https://raw.githubusercontent.com/wordpress-mobile/gutenberg-mobile/v1.85.0/third-party-podspecs/Yoga.podspec.json`)
610610
- ZendeskSupportSDK (= 5.3.0)
611611
- ZIPFoundation (~> 0.9.8)
@@ -868,7 +868,7 @@ SPEC CHECKSUMS:
868868
WordPressKit: d8bc00bce09273fc241cf171b0e61ef8f4f29479
869869
WordPressShared: e5a479220643f46dc4d7726ef8dd45f18bf0c53b
870870
WordPressUI: c5be816f6c7b3392224ac21de9e521e89fa108ac
871-
WPMediaPicker: 749ebfa75fb2b6df4f5e5d9d0847e9512ad74d28
871+
WPMediaPicker: 59135aebb058a95a507045f93e478516729e5c0f
872872
wpxmlrpc: bf55a43a7e710bd2a4fb8c02dfe83b1246f14f13
873873
Yoga: 2ca978c40e0fd6d7f54bcb1602bc0cbbc79454a7
874874
ZendeskCommonUISDK: 5f0a83f412e07ae23701f18c412fe783b3249ef5
@@ -880,6 +880,6 @@ SPEC CHECKSUMS:
880880
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
881881
ZIPFoundation: ae5b4b813d216d3bf0a148773267fff14bd51d37
882882

883-
PODFILE CHECKSUM: 879b8a4d9da3b3bad6cfed2de24977912ce560b0
883+
PODFILE CHECKSUM: 936784ec2e9d8246acccd6b123c0ba5b1b6e13b9
884884

885885
COCOAPODS: 1.11.2

RELEASE-NOTES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
21.2
22
-----
3-
3+
* [*] [internal] Refactored fetching posts in the Reader tab. [#19539]
4+
* [*] Fixed an issue where the message "No media matching your search" for the media picker is not visible [#19555]
45

56
21.1
67
-----

WordPress/Classes/Extensions/Media+Blog.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,19 @@ extension Media {
5959

6060
return media
6161
}
62+
63+
/// Returns a list of Media objects from a post, that should be autoUploaded on the next attempt.
64+
///
65+
/// - Parameters:
66+
/// - post: the post to look auto-uploadable media for.
67+
/// - automatedRetry: whether the media to upload is the result of an automated retry.
68+
///
69+
/// - Returns: the Media objects that should be autoUploaded.
70+
///
71+
class func failedForUpload(in post: AbstractPost, automatedRetry: Bool) -> [Media] {
72+
post.media.filter { media in
73+
media.remoteStatus == .failed
74+
&& (!automatedRetry || media.autoUploadFailureCount.intValue < Media.maxAutoUploadFailureCount)
75+
}
76+
}
6277
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import Foundation
2+
3+
extension Media {
4+
5+
/// Returns a list of Media objects that should be uploaded for the given input parameters.
6+
///
7+
/// - Parameters:
8+
/// - automatedRetry: whether the media to upload is the result of an automated retry.
9+
///
10+
/// - Returns: the Media objects that should be uploaded for the given input parameters.
11+
///
12+
static func failedMediaForUpload(automatedRetry: Bool, in context: NSManagedObjectContext) -> [Media] {
13+
let request = NSFetchRequest<Media>(entityName: Media.entityName())
14+
let failedMediaPredicate = NSPredicate(format: "\(#keyPath(Media.remoteStatusNumber)) == %d", MediaRemoteStatus.failed.rawValue)
15+
16+
if automatedRetry {
17+
let autoUploadFailureCountPredicate = NSPredicate(format: "\(#keyPath(Media.autoUploadFailureCount)) < %d", Media.maxAutoUploadFailureCount)
18+
19+
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [failedMediaPredicate, autoUploadFailureCountPredicate])
20+
} else {
21+
request.predicate = failedMediaPredicate
22+
}
23+
24+
let media = (try? context.fetch(request)) ?? []
25+
26+
return media
27+
}
28+
29+
/// This method checks the status of all media objects and updates them to the correct status if needed.
30+
/// The main cause of wrong status is the app being killed while uploads of media are happening.
31+
///
32+
/// - Parameters:
33+
/// - onCompletion: block to invoke when status update is finished.
34+
/// - onError: block to invoke if any error occurs while the update is being made.
35+
///
36+
static func refreshMediaStatus(using coreDataStack: CoreDataStack, onCompletion: (() -> Void)? = nil, onError: ((Error) -> Void)? = nil) {
37+
coreDataStack.performAndSave { context in
38+
let fetch = NSFetchRequest<Media>(entityName: Media.classNameWithoutNamespaces())
39+
let pushingPredicate = NSPredicate(format: "remoteStatusNumber = %@", NSNumber(value: MediaRemoteStatus.pushing.rawValue))
40+
let processingPredicate = NSPredicate(format: "remoteStatusNumber = %@", NSNumber(value: MediaRemoteStatus.processing.rawValue))
41+
let errorPredicate = NSPredicate(format: "remoteStatusNumber = %@", NSNumber(value: MediaRemoteStatus.failed.rawValue))
42+
fetch.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [pushingPredicate, processingPredicate, errorPredicate])
43+
let mediaPushing = try context.fetch(fetch)
44+
for media in mediaPushing {
45+
// If file were in the middle of being pushed or being processed they now are failed.
46+
if media.remoteStatus == .pushing || media.remoteStatus == .processing {
47+
media.remoteStatus = .failed
48+
}
49+
// If they failed to upload themselfs because no local copy exists then we need to delete this media object
50+
// This scenario can happen when media objects were created based on an asset that failed to import to the WordPress App.
51+
// For example a PHAsset that is stored on the iCloud storage and because of the network connection failed the import process.
52+
if media.remoteStatus == .failed,
53+
let error = media.error as NSError?, error.domain == MediaServiceErrorDomain && error.code == MediaServiceError.fileDoesNotExist.rawValue {
54+
context.delete(media)
55+
}
56+
}
57+
} completion: { result in
58+
DispatchQueue.main.async {
59+
switch result {
60+
case .success:
61+
onCompletion?()
62+
case let .failure(error):
63+
DDLogError("Error while attempting to clean local media: \(error.localizedDescription)")
64+
onError?(error)
65+
}
66+
}
67+
}
68+
}
69+
70+
}

WordPress/Classes/Extensions/UIImageView+SiteIcon.swift

Lines changed: 55 additions & 40 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
}
@@ -145,31 +145,31 @@ extension UIImageView {
145145

146146
// MARK: - Private Methods
147147
//
148-
private extension UIImageView {
148+
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 @@ private 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 @@ private 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+
}

0 commit comments

Comments
 (0)