Skip to content
Merged
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ DerivedData
*.hmap
*.xcscmblueprint

# Claude
.claude/settings.local.json

# Windows
Thumbs.db
ehthumbs.db
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ WordPress-iOS uses a modular architecture with the main app and separate Swift p
- Follow Swift API Design Guidelines
- Use strict access control modifiers where possible
- Use four spaces (not tabs)
- Use semantics text sizes like `.headline`

## Development Workflow
- Branch from `trunk` (main branch)
Expand Down
56 changes: 56 additions & 0 deletions Modules/Sources/WordPressUI/Views/CardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import SwiftUI

/// A reusable card view component that provides a consistent container style
/// with optional title and customizable content.
public struct CardView<Content: View>: View {
let title: String?
@ViewBuilder let content: () -> Content

public init(_ title: String? = nil, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content
}

public var body: some View {
VStack(alignment: .leading, spacing: 16) {
Group {
if let title {
Text(title.uppercased())
.font(.caption)
.foregroundStyle(.secondary)
}
content()
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding()
.clipShape(RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(.separator), lineWidth: 0.5)
)
}
}

#Preview("With Title") {
CardView("Section Title") {
VStack(alignment: .leading, spacing: 12) {
Text("Card Content")
Text("More content here")
.foregroundStyle(.secondary)
}
}
.padding()
}

#Preview("Without Title") {
CardView {
HStack {
Image(systemName: "star.fill")
.foregroundStyle(.yellow)
Text("Featured Item")
Spacer()
}
}
.padding()
}
89 changes: 89 additions & 0 deletions Modules/Sources/WordPressUI/Views/InfoRow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import SwiftUI
import DesignSystem

/// A reusable info row component that displays a title and customizable content.
/// Commonly used within cards or forms to display labeled information.
public struct InfoRow<Content: View>: View {
let title: String
@ViewBuilder let content: () -> Content

public init(_ title: String, @ViewBuilder content: @escaping () -> Content) {
self.title = title
self.content = content
}

public var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.subheadline.weight(.medium))
.lineLimit(1)
content()
.font(.subheadline.weight(.regular))
.lineLimit(1)
.textSelection(.enabled)
}
}
}

// MARK: - Convenience Initializer

extension InfoRow where Content == Text {
/// Convenience initializer for displaying a simple text value.
/// If the value is nil, displays a dash placeholder.
public init(_ title: String, value: String?) {
self.init(title) {
Text(value ?? "–")
.foregroundColor(AppColor.secondary)
}
}
}

// MARK: - Previews

#Preview("Text Value") {
VStack(spacing: 16) {
InfoRow("Email", value: "user@example.com")
InfoRow("Country", value: "United States")
InfoRow("Phone", value: nil)
}
.padding()
}

#Preview("Custom Content") {
VStack(spacing: 16) {
InfoRow("Status") {
HStack(spacing: 4) {
Circle()
.fill(.green)
.frame(width: 8, height: 8)
Text("Active")
.foregroundStyle(.green)
}
}

InfoRow("Website") {
Link("example.com", destination: URL(string: "https://example.com")!)
}

InfoRow("Tags") {
HStack(spacing: 4) {
Text("Swift")
Image(systemName: "chevron.forward")
.font(.caption2)
}
.foregroundStyle(.tint)
}
}
.padding()
}

#Preview("In Card") {
CardView("User Details") {
VStack(spacing: 16) {
InfoRow("Name", value: "John Appleseed")
InfoRow("Email", value: "john@example.com")
InfoRow("Member Since", value: "January 2024")
}
}
.padding()
}
2 changes: 2 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* [**] Add new “Subscribers” screen that shows both your email and Reader subscribers [#24513]
* [*] Fix an issue with “Stats / Subscribers” sometimes not showing the latest email subscribers [#24513]
* [*] Fix an issue with "Stats" / "Subscribers" / "Emails" showing html encoded characters [#24513]
* [*] Add search to “Jetpack Activity List” and display actors and dates [#24597]
* [*] Fix an issue with content in "Restore" and "Download Backup" flows covering the navigation bar [#24597]

25.9
-----
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import UIKit
import SwiftUI
import WordPressKit

/// Coordinator to handle navigation from SwiftUI ActivityLogDetailsView to UIKit view controllers
enum ActivityLogDetailsCoordinator {

static func presentRestore(activity: Activity, blog: Blog) {
guard let viewController = UIViewController.topViewController,
let siteRef = JetpackSiteRef(blog: blog),
activity.isRewindable,
activity.rewindID != nil else {
return
}

// Check if the store has the credentials status cached
let store = StoreContainer.shared.activity
let isAwaitingCredentials = store.isAwaitingCredentials(site: siteRef)

let restoreViewController = JetpackRestoreOptionsViewController(
site: siteRef,
activity: activity,
isAwaitingCredentials: isAwaitingCredentials
)

restoreViewController.presentedFrom = "activity_detail"

let navigationController = UINavigationController(rootViewController: restoreViewController)
navigationController.modalPresentationStyle = .formSheet

viewController.present(navigationController, animated: true)
}

static func presentBackup(activity: Activity, blog: Blog) {
guard let viewController = UIViewController.topViewController,
let siteRef = JetpackSiteRef(blog: blog) else {
return
}

let backupViewController = JetpackBackupOptionsViewController(
site: siteRef,
activity: activity
)

backupViewController.presentedFrom = "activity_detail"

let navigationController = UINavigationController(rootViewController: backupViewController)
navigationController.modalPresentationStyle = .formSheet

viewController.present(navigationController, animated: true)
}
}
Loading