Skip to content

Commit

Permalink
Merge branch 'trunk' into tonyli-use-repository-pages-list
Browse files Browse the repository at this point in the history
  • Loading branch information
crazytonyli authored Nov 22, 2023
2 parents 1a32c7a + d4e4d8d commit 09b060a
Show file tree
Hide file tree
Showing 22 changed files with 407 additions and 59 deletions.
5 changes: 4 additions & 1 deletion RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
23.8
-----
* [**] Add Optimize Images setting for image uploads and enable it by default [#21981]
* [*] Fix the media item details screen layout on iPad [#22042]
* [*] Integrate native photos picker (`PHPickerViewControlle`) in Story Editor [#22059]
* [*] Integrate native photos picker (`PHPickerViewController`) in Story Editor [#22059]
* [*] Fix an issue [#21959] where WordPress → Jetpack migration was not working for accounts with no sites. These users are now presented with a shortened migration flow. The "Uninstall WordPress" prompt will now also appear only as a card on the Dashboard. [#22064]
* [*] [internal] Fix an issue with scheduling of posts not working on iOS 17 with Xcode 15 [#22012]
* [*] [internal] Remove SDWebImage dependency from the app and improve cache cost calculation for GIFs [#21285]
* [*] Stats: Fix an issue where sites for clicked URLs do not open [#22061]
* [*] Improve pages list performance when there are hundreds of pages in the site [#22070]
* [*] [internal] Make Reader web views inspectable on iOS 16.4 and higher [#22077]

23.7
-----
Expand Down
4 changes: 2 additions & 2 deletions WordPress/Classes/Services/MediaCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class MediaCoordinator: NSObject {
addMedia(from: asset, post: post, coordinator: coordinator(for: post), analyticsInfo: analyticsInfo)
}

/// Create a `Media` instance from the main context and upload the asset to the Meida Library.
/// Create a `Media` instance from the main context and upload the asset to the Media Library.
///
/// - Warning: This function must be called from the main thread.
///
Expand Down Expand Up @@ -186,7 +186,7 @@ class MediaCoordinator: NSObject {
return media
}

/// Create a `Media` instance and upload the asset to the Meida Library.
/// Create a `Media` instance and upload the asset to the Media Library.
///
/// - SeeAlso: `MediaImportService.createMedia(with:blog:post:receiveUpdate:thumbnailCallback:completion:)`
private func addMedia(from asset: ExportableAsset, blog: Blog, post: AbstractPost?, coordinator: MediaProgressCoordinator, analyticsInfo: MediaAnalyticsInfo? = nil) {
Expand Down
32 changes: 32 additions & 0 deletions WordPress/Classes/Services/MediaHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,38 @@ class MediaHelper: NSObject {
}

}

static func advertiseImageOptimization(completion: @escaping (() -> Void)) {
guard MediaSettings().advertiseImageOptimization else {
completion()
return
}

let title = NSLocalizedString("appSettings.optimizeImagesPopup.title", value: "Keep optimizing images?",
comment: "Title of an alert informing users to enable image optimization in uploads.")
let message = NSLocalizedString("appSettings.optimizeImagesPopup.message", value: "Image optimization shrinks images for faster uploading.\n\nThis option is enabled by default, but you can change it in the app settings at any time.",
comment: "Message of an alert informing users to enable image optimization in uploads.")
let turnOffTitle = NSLocalizedString("appSettings.optimizeImagesPopup.turnOff", value: "No, turn off", comment: "Title of button for turning off image optimization, displayed in the alert informing users to enable image optimization in uploads.")
let leaveOnTitle = NSLocalizedString("appSettings.optimizeImagesPopup.turnOn", value: "Yes, leave on", comment: "Title of button for leaving on image optimization, displayed in the alert informing users to enable image optimization in uploads.")

let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let turnOffAction = UIAlertAction(title: turnOffTitle, style: .default) { _ in
MediaSettings().imageOptimizationEnabled = false
WPAnalytics.track(.appSettingsOptimizeImagesPopupTapped, properties: ["option": "off"])
completion()
}
let leaveOnAction = UIAlertAction(title: leaveOnTitle, style: .default) { _ in
MediaSettings().imageOptimizationEnabled = true
WPAnalytics.track(.appSettingsOptimizeImagesPopupTapped, properties: ["option": "on"])
completion()
}
alert.addAction(turnOffAction)
alert.addAction(leaveOnAction)
alert.preferredAction = leaveOnAction
alert.presentFromRootViewController()

MediaSettings().advertiseImageOptimization = false
}
}

extension Media {
Expand Down
2 changes: 1 addition & 1 deletion WordPress/Classes/Services/MediaImportService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ class MediaImportService: NSObject {
var options = MediaImageExporter.Options()
options.maximumImageSize = self.exporterMaximumImageSize()
options.stripsGeoLocationIfNeeded = MediaSettings().removeLocationSetting
options.imageCompressionQuality = MediaImportService.preferredImageCompressionQuality
options.imageCompressionQuality = MediaSettings().imageQualityForUpload.doubleValue
return options
}

Expand Down
99 changes: 95 additions & 4 deletions WordPress/Classes/Services/MediaSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,55 @@ import AVFoundation

class MediaSettings: NSObject {
// MARK: - Constants
fileprivate let imageOptimizationKey = "SavedImageOptimizationSetting"
fileprivate let maxImageSizeKey = "SavedMaxImageSizeSetting"
fileprivate let imageQualityKey = "SavedImageQualitySetting"
fileprivate let removeLocationKey = "SavedRemoveLocationSetting"
fileprivate let maxVideoSizeKey = "SavedMaxVideoSizeSetting"
fileprivate let advertiseImageOptimizationKey = "SavedAdvertiseImageOptimization"

fileprivate let defaultImageOptimization = true
fileprivate let defaultMaxImageDimension = 2000
fileprivate let defaultImageQuality: ImageQuality = .medium
fileprivate let defaultMaxVideoSize: VideoResolution = .sizeOriginal
fileprivate let defaultRemoveLocation = true

fileprivate let minImageDimension = 150
fileprivate let maxImageDimension = 3000

enum ImageQuality: String {
case maximum = "MaximumQuality100"
case high = "HighQuality90"
case medium = "MediumQuality80"
case low = "LowQuality70"

var doubleValue: Double {
switch self {
case .maximum:
return 1.0
case .high:
return 0.9
case .medium:
return 0.8
case .low:
return 0.7
}
}

var description: String {
switch self {
case .maximum:
return NSLocalizedString("appSettings.media.imageQuality.maximum", value: "Maximum", comment: "Indicates an image will use maximum quality when uploaded.")
case .high:
return NSLocalizedString("appSettings.media.imageQuality.high", value: "High", comment: "Indicates an image will use high quality when uploaded.")
case .medium:
return NSLocalizedString("appSettings.media.imageQuality.medium", value: "Medium", comment: "Indicates an image will use medium quality when uploaded.")
case(.low):
return NSLocalizedString("appSettings.media.imageQuality.low", value: "Low", comment: "Indicates an image will use low quality when uploaded.")
}
}
}

enum VideoResolution: String {
case size640x480 = "AVAssetExportPreset640x480"
case size1280x720 = "AVAssetExportPreset1280x720"
Expand Down Expand Up @@ -110,13 +151,19 @@ class MediaSettings: NSObject {
/// - Note: if the image doesn't need to be resized, it returns `Int.max`
///
@objc var imageSizeForUpload: Int {
if maxImageSizeSetting >= maxImageDimension {
// When image optimization is enabled, setting the max image size setting to
// the maximum value will be considered as to using the original size.
if !imageOptimizationEnabled || maxImageSizeSetting >= maxImageDimension {
return Int.max
} else {
return maxImageSizeSetting
}
}

var imageQualityForUpload: ImageQuality {
return imageOptimizationEnabled ? imageQualitySetting : .high
}

/// The stored value for the maximum size images can have before uploading.
/// If you set this to `maxImageDimension` or higher, it means images won't
/// be resized on upload.
Expand All @@ -134,7 +181,7 @@ class MediaSettings: NSObject {
database.set(newSize, forKey: maxImageSizeKey)
return Int(newSize)
} else {
return maxImageDimension
return defaultMaxImageDimension
}
}
set {
Expand All @@ -148,7 +195,7 @@ class MediaSettings: NSObject {
if let savedRemoveLocation = database.object(forKey: removeLocationKey) as? Bool {
return savedRemoveLocation
} else {
return true
return defaultRemoveLocation
}
}
set {
Expand All @@ -160,12 +207,56 @@ class MediaSettings: NSObject {
get {
guard let savedSize = database.object(forKey: maxVideoSizeKey) as? String,
let videoSize = VideoResolution(rawValue: savedSize) else {
return .sizeOriginal
return defaultMaxVideoSize
}
return videoSize
}
set {
database.set(newValue.rawValue, forKey: maxVideoSizeKey)
}
}

var imageOptimizationEnabled: Bool {
get {
if let savedImageOptimization = database.object(forKey: imageOptimizationKey) as? Bool {
return savedImageOptimization
} else {
return defaultImageOptimization
}
}
set {
database.set(newValue, forKey: imageOptimizationKey)

// If the user changes this setting manually, we disable the image optimization popup.
if advertiseImageOptimization {
advertiseImageOptimization = false
}
}
}

var imageQualitySetting: ImageQuality {
get {
guard let savedQuality = database.object(forKey: imageQualityKey) as? String,
let imageQuality = ImageQuality(rawValue: savedQuality) else {
return defaultImageQuality
}
return imageQuality
}
set {
database.set(newValue.rawValue, forKey: imageQualityKey)
}
}

var advertiseImageOptimization: Bool {
get {
if let savedAdvertiseImageOptimization = database.object(forKey: advertiseImageOptimizationKey) as? Bool {
return savedAdvertiseImageOptimization
} else {
return true
}
}
set {
database.set(newValue, forKey: advertiseImageOptimizationKey)
}
}
}
12 changes: 12 additions & 0 deletions WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,14 @@ import Foundation
case accountCloseCompleted

// App Settings
case appSettingsOptimizeImagesChanged
case appSettingsMaxImageSizeChanged
case appSettingsImageQualityChanged
case appSettingsClearMediaCacheTapped
case appSettingsClearSpotlightIndexTapped
case appSettingsClearSiriSuggestionsTapped
case appSettingsOpenDeviceSettingsTapped
case appSettingsOptimizeImagesPopupTapped

// Notifications
case notificationsPreviousTapped
Expand Down Expand Up @@ -1013,6 +1017,14 @@ import Foundation
return "app_settings_clear_siri_suggestions_tapped"
case .appSettingsOpenDeviceSettingsTapped:
return "app_settings_open_device_settings_tapped"
case .appSettingsOptimizeImagesChanged:
return "app_settings_optimize_images_changed"
case .appSettingsMaxImageSizeChanged:
return "app_settings_max_image_size_changed"
case .appSettingsImageQualityChanged:
return "app_settings_image_quality_changed"
case .appSettingsOptimizeImagesPopupTapped:
return "app_settings_optimize_images_popup_tapped"

// Account Close
case .accountCloseTapped:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,24 @@
return
}

let hasBlogs = AccountHelper.hasBlogs

guard isLocalPostsSynced() else {
let error = MigrationError.localDraftsNotSynced
tracker.trackContentExportFailed(reason: error.localizedDescription)
tracker.trackContentExportFailed(reason: error.localizedDescription, hasBlogs: hasBlogs)
processResult(.failure(error), completion: completion)
return
}

dataMigrator.exportData { [weak self] result in
switch result {
case .success:
self?.tracker.trackContentExportSucceeded()
self?.tracker.trackContentExportSucceeded(hasBlogs: hasBlogs)
self?.processResult(.success(()), completion: completion)

case .failure(let error):
DDLogError("[Jetpack Migration] Error exporting data: \(error)")
self?.tracker.trackContentExportFailed(reason: error.localizedDescription)
self?.tracker.trackContentExportFailed(reason: error.localizedDescription, hasBlogs: hasBlogs)
self?.processResult(.failure(.exportFailure), completion: completion)
}
}
Expand Down Expand Up @@ -187,7 +189,7 @@ protocol ContentMigrationEligibilityProvider {

extension AppConfiguration: ContentMigrationEligibilityProvider {
var isEligibleForMigration: Bool {
Self.isWordPress && AccountHelper.isLoggedIn && AccountHelper.hasBlogs
Self.isWordPress && AccountHelper.isLoggedIn
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ struct ImageSizeModel: MediaSizeModel {
if value == maxValue {
return NSLocalizedString("Original", comment: "Indicates an image will use its original size when uploaded.")
}
let format = NSLocalizedString("%dx%dpx", comment: "Max image size in pixels (e.g. 300x300px)")
let format = NSLocalizedString("mediaSizeSlider.valueFormat", value: "%d × %d px", comment: "Max image size in pixels (e.g. 300x300px)")
return String(format: format, value, value)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ extension GutenbergMediaPickerHelper: ImagePickerControllerDelegate {
switch mediaType {
case UTType.image.identifier:
if let image = info[.originalImage] as? UIImage {
self.didPickMediaCallback?([image])
self.didPickMediaCallback = nil
MediaHelper.advertiseImageOptimization() { [self] in
self.didPickMediaCallback?([image])
self.didPickMediaCallback = nil
}
}

case UTType.movie.identifier:
Expand Down Expand Up @@ -108,7 +110,20 @@ extension GutenbergMediaPickerHelper: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
context.dismiss(animated: true)

didPickMediaCallback?(results.map(\.itemProvider))
didPickMediaCallback = nil
guard results.count > 0 else {
return
}

let mediaFilter = picker.configuration.filter
if mediaFilter == PHPickerFilter(.all) || mediaFilter == PHPickerFilter(.image) {
MediaHelper.advertiseImageOptimization() { [self] in
didPickMediaCallback?(results.map(\.itemProvider))
didPickMediaCallback = nil
}
}
else {
didPickMediaCallback?(results.map(\.itemProvider))
didPickMediaCallback = nil
}
}
}
Loading

0 comments on commit 09b060a

Please sign in to comment.