Skip to content

Commit c8f27c5

Browse files
committed
Merge branch 'trunk' into wordpresskit-error-refactor
2 parents a3d03a6 + f075529 commit c8f27c5

File tree

55 files changed

+761
-49
lines changed

Some content is hidden

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

55 files changed

+761
-49
lines changed

Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ abstract_target 'Apps' do
114114
pod 'AppCenter', app_center_version, configurations: app_center_configurations
115115
pod 'AppCenter/Distribute', app_center_version, configurations: app_center_configurations
116116

117-
pod 'Starscream', '3.0.6'
117+
pod 'Starscream', '~> 4.0'
118118
pod 'SVProgressHUD', '2.2.5'
119119
pod 'ZendeskSupportSDK', '5.3.0'
120120
pod 'AlamofireImage', '3.5.2'

Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ PODS:
5656
- SentryPrivate (= 8.18.0)
5757
- SentryPrivate (8.18.0)
5858
- Sodium (0.9.1)
59-
- Starscream (3.0.6)
59+
- Starscream (4.0.6)
6060
- SVProgressHUD (2.2.5)
6161
- SwiftLint (0.54.0)
6262
- UIDeviceIdentifier (2.3.0)
@@ -115,7 +115,7 @@ DEPENDENCIES:
115115
- OCMock (~> 3.4.3)
116116
- OHHTTPStubs/Swift (~> 9.1.0)
117117
- Reachability (= 3.2)
118-
- Starscream (= 3.0.6)
118+
- Starscream (~> 4.0)
119119
- SVProgressHUD (= 2.2.5)
120120
- SwiftLint (~> 0.50)
121121
- WordPress-Editor-iOS (~> 1.19.9)
@@ -216,7 +216,7 @@ SPEC CHECKSUMS:
216216
Sentry: 8984a4ffb2b9bd2894d74fb36e6f5833865bc18e
217217
SentryPrivate: 2f0c9ba4c3fc993f70eab6ca95673509561e0085
218218
Sodium: 23d11554ecd556196d313cf6130d406dfe7ac6da
219-
Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
219+
Starscream: fb2c4510bebf908c62bd383bcf05e673720e91fd
220220
SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
221221
SwiftLint: c1de071d9d08c8aba837545f6254315bc900e211
222222
UIDeviceIdentifier: 442b65b4ff1832d4ca9c2a157815cb29ad981b17
@@ -236,6 +236,6 @@ SPEC CHECKSUMS:
236236
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
237237
ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced
238238

239-
PODFILE CHECKSUM: e000a2048651eeea961e5c16e130a60d9c7cd1b9
239+
PODFILE CHECKSUM: 91ca97cb58a7ec8391611ea499d341d47f633cda
240240

241241
COCOAPODS: 1.14.2

RELEASE-NOTES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
24.3
22
-----
3+
* [***] [internal] Refactored websocket connections to Pinghub. [#22611]
4+
35
* [**] Multiple pre-publishing sheet fixes and improvements [#22606]
46
* [*] Gravatar: Adds informative new view about Gravatar to the profile editing page. [#22615]
57
* [**] [internal] Refactored .org REST API calls. [#22612]

WordPress/Classes/Networking/Pinghub.swift

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ public class PinghubClient {
4646

4747
/// Initializes the client with an OAuth2 token.
4848
///
49-
public convenience init(token: String) {
50-
let socket = starscreamSocket(url: PinghubClient.endpoint, token: token)
49+
public convenience init(token: String, endpoint: URL? = nil) {
50+
let socket = starscreamSocket(url: endpoint ?? PinghubClient.endpoint, token: token)
5151
self.init(socket: socket)
5252
}
5353

@@ -83,14 +83,17 @@ public class PinghubClient {
8383
guard let client = self else {
8484
return
8585
}
86-
let error = error as NSError?
87-
let filteredError: NSError? = error.flatMap({ error in
88-
// Filter out normal disconnects that we initiated
89-
if error.domain == WebSocket.ErrorDomain && error.code == Int(CloseCode.normal.rawValue) {
90-
return nil
91-
}
92-
return error
93-
})
86+
87+
let filteredError: Error?
88+
89+
if (error as? WSError)?.code == UInt16(CloseCode.normal.rawValue) {
90+
filteredError = nil
91+
} else if (error as? ConnectionClosed)?.code == .normalClosure {
92+
filteredError = nil
93+
} else {
94+
filteredError = error
95+
}
96+
9497
client.delegate?.pinghubDidDisconnect(client, error: filteredError)
9598
}
9699
socket.onData = { [weak self] data in
@@ -218,13 +221,85 @@ internal protocol Socket: AnyObject {
218221
// MARK: - Starscream
219222

220223
private func starscreamSocket(url: URL, token: String) -> Socket {
221-
var request = URLRequest(url: PinghubClient.endpoint)
224+
var request = URLRequest(url: url)
222225
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
223-
return WebSocket(request: request)
226+
return StarscreamWebSocket(socket: WebSocket(request: request))
227+
}
228+
229+
private class StarscreamWebSocket: Socket {
230+
231+
var onConnect: (() -> Void)?
232+
var onDisconnect: ((Error?) -> Void)?
233+
var onText: ((String) -> Void)?
234+
var onData: ((Data) -> Void)?
235+
236+
let socket: WebSocket
237+
238+
init(socket: WebSocket) {
239+
self.socket = socket
240+
241+
socket.onEvent = { [weak self] event in
242+
guard let self else { return }
243+
244+
switch event {
245+
case .connected:
246+
self.onConnect?()
247+
case let .disconnected(_, code):
248+
self.onDisconnect?(ConnectionClosed(code: Int(code)))
249+
case .peerClosed:
250+
// Remote peer has closed the network connection. See `ConnectionState.peerClosed`.
251+
self.onDisconnect?(ConnectionClosed(code: .normal))
252+
case let .text(text):
253+
self.onText?(text)
254+
case let .binary(data):
255+
self.onData?(data)
256+
case let .error(error):
257+
self.onDisconnect?(error)
258+
case .pong, .ping, .viabilityChanged, .reconnectSuggested, .cancelled:
259+
break
260+
}
261+
}
262+
}
263+
264+
func connect() {
265+
socket.connect()
266+
}
267+
268+
func disconnect() {
269+
socket.disconnect()
270+
}
271+
224272
}
225273

226-
extension WebSocket: Socket {
227-
@objc func disconnect() {
228-
disconnect(forceTimeout: nil)
274+
private struct ConnectionClosed: Error {
275+
var code: URLSessionWebSocketTask.CloseCode
276+
277+
init(code: Int) {
278+
self.code = .init(rawValue: code) ?? .protocolError
279+
}
280+
281+
init(code: URLSessionWebSocketTask.CloseCode) {
282+
self.code = code
283+
}
284+
285+
init(code: Starscream.CloseCode) {
286+
switch code {
287+
case .normal:
288+
self.code = .normalClosure
289+
case .goingAway:
290+
self.code = .goingAway
291+
case .protocolError:
292+
self.code = .protocolError
293+
case .protocolUnhandledType:
294+
self.code = .unsupportedData
295+
case .noStatusReceived:
296+
self.code = .noStatusReceived
297+
case .encoding:
298+
self.code = .invalidFramePayloadData
299+
case .policyViolated:
300+
self.code = .policyViolation
301+
case .messageTooBig:
302+
self.code = .messageTooBig
303+
}
229304
}
230305
}

WordPress/Classes/ViewRelated/Jetpack/Branding/Coordinator/JetpackFeaturesRemovalCoordinator.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,6 @@ class JetpackFeaturesRemovalCoordinator: NSObject {
238238

239239
let coordinator = JetpackDefaultOverlayCoordinator()
240240
let viewModel = JetpackFullscreenOverlayGeneralViewModel(phase: phase, source: source, blog: blog, coordinator: coordinator)
241-
let overlayViewController = JetpackFullscreenOverlayViewController(with: viewModel)
242-
let navigationViewController = UINavigationController(rootViewController: overlayViewController)
243-
coordinator.navigationController = navigationViewController
244-
coordinator.viewModel = viewModel
245-
viewModel.onWillDismiss = onWillDismiss
246-
viewModel.onDidDismiss = onDidDismiss
247241
let frequencyTracker = OverlayFrequencyTracker(source: source,
248242
type: .featuresRemoval,
249243
frequencyConfig: frequencyConfig,
@@ -253,6 +247,12 @@ class JetpackFeaturesRemovalCoordinator: NSObject {
253247
onDidDismiss?()
254248
return
255249
}
250+
let overlayViewController = JetpackFullscreenOverlayViewController(with: viewModel)
251+
let navigationViewController = UINavigationController(rootViewController: overlayViewController)
252+
coordinator.navigationController = navigationViewController
253+
coordinator.viewModel = viewModel
254+
viewModel.onWillDismiss = onWillDismiss
255+
viewModel.onDidDismiss = onDidDismiss
256256
presentOverlay(navigationViewController: navigationViewController, in: viewController, fullScreen: fullScreen)
257257
frequencyTracker.track()
258258
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import SwiftUI
2+
3+
struct PublishButton: View {
4+
@ObservedObject var viewModel: PublishButtonViewModel
5+
6+
var body: some View {
7+
ZStack {
8+
Button(action: viewModel.onSubmitTapped) {
9+
Text(viewModel.title)
10+
.font(.title3.weight(.medium))
11+
.frame(maxWidth: .infinity)
12+
.opacity(isDisabled ? 0 : 1)
13+
}
14+
.buttonStyle(.borderedProminent)
15+
.controlSize(.large)
16+
.disabled(isDisabled)
17+
.buttonBorderShape(.roundedRectangle(radius: 8))
18+
19+
switch viewModel.state {
20+
case .default:
21+
EmptyView()
22+
case .loading:
23+
ProgressView()
24+
.tint(Color.secondary)
25+
case let .uploading(title, progress):
26+
HStack(spacing: 10) {
27+
ProgressView()
28+
.tint(Color.secondary)
29+
30+
VStack(alignment: .leading) {
31+
Text(title)
32+
.font(.subheadline.weight(.medium))
33+
if let progress {
34+
Text(Strings.progress(progress))
35+
.foregroundStyle(Color.secondary)
36+
.font(.footnote)
37+
.monospacedDigit()
38+
}
39+
}
40+
.lineLimit(1)
41+
.foregroundStyle(Color.primary)
42+
43+
Spacer()
44+
}
45+
.padding(.horizontal)
46+
case let .failed(title, details, onRetryTapped):
47+
HStack {
48+
Image(systemName: "exclamationmark.triangle.fill")
49+
.foregroundStyle(Color.red)
50+
VStack(alignment: .leading) {
51+
Text(title)
52+
.font(.subheadline.weight(.medium))
53+
.foregroundStyle(.primary)
54+
if let details {
55+
Text(details)
56+
.font(.footnote)
57+
.foregroundStyle(.secondary)
58+
}
59+
}
60+
.lineLimit(1)
61+
62+
Spacer()
63+
64+
if let onRetryTapped {
65+
Button(Strings.retry, action: onRetryTapped)
66+
.font(.subheadline)
67+
}
68+
}
69+
.padding(.horizontal)
70+
}
71+
}
72+
}
73+
74+
private var isDisabled: Bool {
75+
switch viewModel.state {
76+
case .default: false
77+
case .loading, .uploading, .failed: true
78+
}
79+
}
80+
}
81+
82+
final class PublishButtonViewModel: ObservableObject {
83+
let title: String
84+
let onSubmitTapped: () -> Void
85+
@Published var state: PublishButtonState = .default
86+
87+
init(title: String, onSubmitTapped: @escaping () -> Void, state: PublishButtonState = .default) {
88+
self.title = title
89+
self.onSubmitTapped = onSubmitTapped
90+
self.state = state
91+
}
92+
}
93+
94+
enum PublishButtonState {
95+
case `default`
96+
case loading
97+
case uploading(title: String, progress: Progress?)
98+
case failed(title: String, details: String? = nil, onRetryTapped: (() -> Void)? = nil)
99+
100+
struct Progress {
101+
let completed: Int64
102+
let total: Int64
103+
}
104+
}
105+
106+
private enum Strings {
107+
static func progress(_ progress: PublishButtonState.Progress) -> String {
108+
let format = NSLocalizedString("publishButton.progress", value: "%@ of %@", comment: "Shows the download or upload progress with two parameters: preformatted completed and total bytes")
109+
return String(format: format, ByteCountFormatter.string(fromByteCount: progress.completed, countStyle: .file), ByteCountFormatter.string(fromByteCount: progress.total, countStyle: .file))
110+
}
111+
112+
static let retry = NSLocalizedString("publishButton.retry", value: "Retry", comment: "Retry button title")
113+
}
114+
115+
#Preview {
116+
VStack(spacing: 16) {
117+
PublishButton(viewModel: .init(title: "Publish", onSubmitTapped: {}, state: .default))
118+
PublishButton(viewModel: .init(title: "Publish", onSubmitTapped: {}, state: .loading))
119+
PublishButton(viewModel: .init(title: "Publish", onSubmitTapped: {}, state: .uploading(title: "Uploading media...", progress: .init(completed: 100, total: 2000))))
120+
PublishButton(viewModel: .init(title: "Publish", onSubmitTapped: {}, state: .failed(title: "Failed to upload media")))
121+
PublishButton(viewModel: .init(title: "Publish", onSubmitTapped: {}, state: .failed(title: "Failed to upload media", details: "Not connected to Internet", onRetryTapped: {})))
122+
}
123+
.padding()
124+
}

WordPress/Classes/ViewRelated/Reader/ReaderCommentAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ final class ReaderCommentAction {
1313
controller.navigateToCommentID = navigateToCommentID as NSNumber?
1414
controller.promptToAddComment = promptToAddComment
1515
controller.trackCommentsOpened()
16+
controller.hidesBottomBarWhenPushed = true
1617
origin.navigationController?.pushViewController(controller, animated: true)
1718
}
1819
}

WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,8 @@ private extension StatsTotalRow {
233233
func applyStyles() {
234234
backgroundColor = .listForeground
235235
contentView.backgroundColor = .listForeground
236-
Style.configureLabelAsCellValueTitle(itemLabel)
237-
Style.configureLabelAsCellValue(itemDetailLabel)
236+
Style.configureLabelAsCellRowTitle(itemLabel)
237+
Style.configureLabelItemDetail(itemDetailLabel)
238238
Style.configureLabelAsData(dataLabel)
239239
Style.configureViewAsSeparator(separatorLine)
240240
Style.configureViewAsSeparator(topExpandedSeparatorLine)

WordPress/Resources/ar.lproj/Localizable.strings

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Translation-Revision-Date: 2024-02-12 21:54:07+0000 */
1+
/* Translation-Revision-Date: 2024-02-14 13:54:08+0000 */
22
/* Plural-Forms: nplurals=6; plural=(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5)))); */
33
/* Generator: GlotPress/4.0.0-beta.2 */
44
/* Language: ar */
@@ -11108,6 +11108,9 @@ Example: given a notice format "Following %@" and empty site name, this will be
1110811108
/* Title for PHP logs screen. */
1110911109
"siteMonitoring.phpLogs" = "سجلات PHP";
1111011110

11111+
/* Title for web server log screen. */
11112+
"siteMonitoring.webServerLogs" = "سجلات خادم الويب";
11113+
1111111114
/* Title for screen to select the privacy options for a blog */
1111211115
"siteSettings.privacy.title" = "الخصوصية";
1111311116

WordPress/Resources/de.lproj/Localizable.strings

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Translation-Revision-Date: 2024-02-12 11:54:07+0000 */
1+
/* Translation-Revision-Date: 2024-02-13 14:31:33+0000 */
22
/* Plural-Forms: nplurals=2; plural=n != 1; */
33
/* Generator: GlotPress/4.0.0-beta.2 */
44
/* Language: de */
@@ -11173,6 +11173,9 @@ Example: given a notice format "Following %@" and empty site name, this will be
1117311173
/* Title for PHP logs screen. */
1117411174
"siteMonitoring.phpLogs" = "PHP-Protokolle";
1117511175

11176+
/* Title for web server log screen. */
11177+
"siteMonitoring.webServerLogs" = "Webserver-Protokolle";
11178+
1117611179
/* Title for screen to select the privacy options for a blog */
1117711180
"siteSettings.privacy.title" = "Datenschutz";
1117811181

0 commit comments

Comments
 (0)