Skip to content

Commit 1135ced

Browse files
committed
Refactor About view to use shared SettingsRow
Introduce a reusable SettingsRow component (CycleOne/Views/Shared/SettingsRow.swift) and replace local AboutRow/SettingsRow implementations. Convert AboutView to use a List with Sections and SettingsRow entries for Developer, Project and Footer content, adjust layout (logo size, paddings, corner radii, and copy tweaks), and wire Links to use the new row component. Remove duplicated row view from SettingsView and clean up the AboutView layout for a more consistent, native settings appearance.
1 parent b907f9f commit 1135ced

3 files changed

Lines changed: 153 additions & 204 deletions

File tree

CycleOne/Views/Settings/AboutView.swift

Lines changed: 96 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -9,214 +9,126 @@ struct AboutView: View {
99
@EnvironmentObject private var themeManager: ThemeManager
1010

1111
var body: some View {
12-
ScrollView {
13-
VStack(spacing: 20) {
14-
// App Header
15-
VStack(spacing: 12) {
16-
Image("AppLogo")
17-
.resizable()
18-
.scaledToFit()
19-
.frame(width: 100, height: 100)
20-
.clipShape(
21-
RoundedRectangle(cornerRadius: 24)
22-
)
23-
.shadow(
24-
color: Color.themeAccent
25-
.opacity(0.15),
26-
radius: 12, x: 0, y: 6
27-
)
12+
List {
13+
// App Header Section
14+
Section {
15+
HStack {
16+
Spacer()
17+
VStack(spacing: 12) {
18+
Image("AppLogo")
19+
.resizable()
20+
.scaledToFit()
21+
.frame(width: 80, height: 80)
22+
.clipShape(RoundedRectangle(cornerRadius: 18))
23+
.shadow(
24+
color: Color.themeAccent.opacity(0.15),
25+
radius: 10, x: 0, y: 5
26+
)
2827

29-
Text("CycleOne")
30-
.font(.system(
31-
.title2, design: .rounded
32-
))
33-
.fontWeight(.bold)
34-
.foregroundColor(.themeAccent)
28+
VStack(spacing: 4) {
29+
Text("CycleOne")
30+
.font(.system(.title3, design: .rounded))
31+
.fontWeight(.bold)
32+
.foregroundColor(.themeAccent)
3533

36-
Text("Version 1.0.0")
37-
.font(.caption)
38-
.foregroundColor(.secondary)
34+
Text("Version 1.0.0")
35+
.font(.caption)
36+
.foregroundColor(.secondary)
37+
}
3938

40-
Text(
41-
"A privacy-first, open-source period "
42-
+ "tracker for iOS."
43-
)
44-
.font(.subheadline)
45-
.foregroundColor(.secondary)
46-
.multilineTextAlignment(.center)
47-
.padding(.horizontal, 32)
39+
Text("A privacy-first, open-source period tracker for iOS.")
40+
.font(.subheadline)
41+
.foregroundColor(.secondary)
42+
.multilineTextAlignment(.center)
43+
.padding(.horizontal, 20)
44+
}
45+
.padding(.vertical, 16)
46+
Spacer()
4847
}
49-
.padding(.top, 16)
50-
51-
// Developer Section
52-
VStack(alignment: .leading, spacing: 0) {
53-
Text("Developer")
54-
.font(.subheadline)
55-
.fontWeight(.semibold)
56-
.foregroundColor(.secondary)
57-
.padding(.horizontal, 20)
58-
.padding(.bottom, 8)
59-
60-
AboutRow(
61-
icon: "person.fill",
62-
title: "VoxDroid",
63-
subtitle: "Developer",
64-
color: .themeAccent
65-
)
48+
.listRowBackground(Color.clear)
49+
}
6650

67-
Divider().padding(.leading, 56)
51+
// Developer Section
52+
Section("Developer") {
53+
SettingsRow(
54+
icon: "person.fill",
55+
title: "VoxDroid",
56+
subtitle: "Lead Developer",
57+
color: .themeAccent
58+
)
6859

69-
if let mailURL = URL(
70-
string: "mailto:izeno.contact@gmail.com"
71-
) {
72-
Link(destination: mailURL) {
73-
AboutRow(
74-
icon: "envelope.fill",
75-
title:
76-
"izeno.contact@gmail.com",
77-
subtitle: "Email",
78-
color: .blue,
79-
showChevron: true
80-
)
81-
}
60+
if let mailURL = URL(string: "mailto:izeno.contact@gmail.com") {
61+
Link(destination: mailURL) {
62+
SettingsRow(
63+
icon: "envelope.fill",
64+
title: "izeno.contact@gmail.com",
65+
subtitle: "Direct Support",
66+
color: .blue,
67+
showChevron: true
68+
)
8269
}
70+
}
8371

84-
Divider().padding(.leading, 56)
85-
86-
if let ghURL = URL(
87-
string:
88-
"https://github.com/VoxDroid"
89-
) {
90-
Link(destination: ghURL) {
91-
AboutRow(
92-
icon: "link",
93-
title: "github.com/VoxDroid",
94-
subtitle: "GitHub",
95-
color: .purple,
96-
showChevron: true
97-
)
98-
}
72+
if let ghURL = URL(string: "https://github.com/VoxDroid") {
73+
Link(destination: ghURL) {
74+
SettingsRow(
75+
icon: "link",
76+
title: "github.com/VoxDroid",
77+
subtitle: "Follow on GitHub",
78+
color: .purple,
79+
showChevron: true
80+
)
9981
}
10082
}
101-
.background(
102-
RoundedRectangle(
103-
cornerRadius: Theme.cornerRadius
104-
)
105-
.fill(Color(.secondarySystemBackground))
106-
.padding(.horizontal, 16)
107-
)
108-
109-
// Project Section
110-
VStack(alignment: .leading, spacing: 0) {
111-
Text("Project")
112-
.font(.subheadline)
113-
.fontWeight(.semibold)
114-
.foregroundColor(.secondary)
115-
.padding(.horizontal, 20)
116-
.padding(.bottom, 8)
83+
}
11784

118-
if let repoURL = URL(
119-
string:
120-
"https://github.com/VoxDroid/CycleOne"
121-
) {
122-
Link(destination: repoURL) {
123-
AboutRow(
124-
icon: "chevron.left.forwardslash.chevron.right",
125-
title: "Source Code",
126-
subtitle: "GPL-3.0 Licensed",
127-
color: .green,
128-
showChevron: true
129-
)
130-
}
85+
// Project Section
86+
Section("Project") {
87+
if let repoURL = URL(string: "https://github.com/VoxDroid/CycleOne") {
88+
Link(destination: repoURL) {
89+
SettingsRow(
90+
icon: "chevron.left.forwardslash.chevron.right",
91+
title: "Source Code",
92+
subtitle: "GPL-3.0 Licensed",
93+
color: .green,
94+
showChevron: true
95+
)
13196
}
97+
}
13298

133-
Divider().padding(.leading, 56)
134-
135-
AboutRow(
136-
icon: "shield.fill",
137-
title: "Privacy",
138-
subtitle:
139-
"100% local, zero tracking",
140-
color: .indigo
141-
)
142-
143-
Divider().padding(.leading, 56)
99+
SettingsRow(
100+
icon: "shield.fill",
101+
title: "Privacy Commitment",
102+
subtitle: "100% local, zero tracking",
103+
color: .indigo
104+
)
144105

145-
AboutRow(
146-
icon: "swift",
147-
title: "Built with Swift",
148-
subtitle:
149-
"SwiftUI + Core Data",
150-
color: .orange
151-
)
152-
}
153-
.background(
154-
RoundedRectangle(
155-
cornerRadius: Theme.cornerRadius
156-
)
157-
.fill(Color(.secondarySystemBackground))
158-
.padding(.horizontal, 16)
106+
SettingsRow(
107+
icon: "swift",
108+
title: "Built with Swift",
109+
subtitle: "SwiftUI + Core Data",
110+
color: .orange
159111
)
112+
}
160113

161-
// Footer
162-
VStack(spacing: 4) {
114+
// Footer Section
115+
Section {
116+
VStack(spacing: 6) {
163117
Text("CycleOne by VoxDroid")
164118
.font(.caption)
165119
.fontWeight(.medium)
166120
.foregroundColor(.secondary)
167-
Text(
168-
"\u{00A9} 2026 VoxDroid. "
169-
+ "All rights reserved."
170-
)
171-
.font(.caption2)
172-
.foregroundColor(
173-
.secondary.opacity(0.7)
174-
)
121+
122+
Text("\u{00A9} 2026 VoxDroid. All rights reserved.")
123+
.font(.caption2)
124+
.foregroundColor(.secondary.opacity(0.7))
175125
}
176-
.padding(.top, 8)
177-
.padding(.bottom, 24)
126+
.frame(maxWidth: .infinity)
127+
.listRowBackground(Color.clear)
128+
.padding(.vertical, 8)
178129
}
179130
}
180-
.background(Color(.systemGroupedBackground))
181131
.navigationTitle("About")
182132
.navigationBarTitleDisplayMode(.inline)
183133
}
184134
}
185-
186-
private struct AboutRow: View {
187-
let icon: String
188-
let title: String
189-
let subtitle: String
190-
let color: Color
191-
var showChevron: Bool = false
192-
193-
var body: some View {
194-
HStack(spacing: 12) {
195-
Image(systemName: icon)
196-
.font(.system(size: 14))
197-
.foregroundColor(.white)
198-
.frame(width: 28, height: 28)
199-
.background(color)
200-
.cornerRadius(6)
201-
202-
VStack(alignment: .leading, spacing: 1) {
203-
Text(title)
204-
.font(.subheadline)
205-
.foregroundColor(.primary)
206-
Text(subtitle)
207-
.font(.caption2)
208-
.foregroundColor(.secondary)
209-
}
210-
211-
Spacer()
212-
213-
if showChevron {
214-
Image(systemName: "chevron.right")
215-
.font(.caption2)
216-
.foregroundColor(.secondary)
217-
}
218-
}
219-
.padding(.horizontal, 20)
220-
.padding(.vertical, 10)
221-
}
222-
}

CycleOne/Views/Settings/SettingsView.swift

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -276,23 +276,3 @@ struct SettingsView: View {
276276
PersistenceController.shared.container.viewContext.reset()
277277
}
278278
}
279-
280-
private struct SettingsRow: View {
281-
let icon: String
282-
let title: String
283-
let color: Color
284-
285-
var body: some View {
286-
HStack(spacing: 12) {
287-
Image(systemName: icon)
288-
.font(.system(size: 14))
289-
.foregroundColor(.white)
290-
.frame(width: 28, height: 28)
291-
.background(color)
292-
.cornerRadius(6)
293-
294-
Text(title)
295-
.foregroundColor(.primary)
296-
}
297-
}
298-
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// SettingsRow.swift
3+
// CycleOne
4+
//
5+
6+
import SwiftUI
7+
8+
struct SettingsRow: View {
9+
let icon: String
10+
let title: String
11+
let subtitle: String?
12+
let color: Color
13+
let showChevron: Bool
14+
15+
init(
16+
icon: String,
17+
title: String,
18+
subtitle: String? = nil,
19+
color: Color,
20+
showChevron: Bool = false
21+
) {
22+
self.icon = icon
23+
self.title = title
24+
self.subtitle = subtitle
25+
self.color = color
26+
self.showChevron = showChevron
27+
}
28+
29+
var body: some View {
30+
HStack(spacing: 12) {
31+
Image(systemName: icon)
32+
.font(.system(size: 14))
33+
.foregroundColor(.white)
34+
.frame(width: 28, height: 28)
35+
.background(color)
36+
.cornerRadius(6)
37+
38+
VStack(alignment: .leading, spacing: 1) {
39+
Text(title)
40+
.font(.subheadline)
41+
.foregroundColor(.primary)
42+
if let subtitle {
43+
Text(subtitle)
44+
.font(.caption2)
45+
.foregroundColor(.secondary)
46+
}
47+
}
48+
49+
if showChevron {
50+
Spacer()
51+
Image(systemName: "chevron.right")
52+
.font(.caption2)
53+
.foregroundColor(.secondary)
54+
}
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)