Skip to content

Commit 63f4c49

Browse files
BohdanBohdan
authored andcommitted
Enhanced About page
1 parent e3ec680 commit 63f4c49

File tree

2 files changed

+166
-55
lines changed

2 files changed

+166
-55
lines changed

LanguageFlag/Stories/Preferences/Panes/AboutPreferencesPane.swift

Lines changed: 164 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import SwiftUI
22

3-
/// About preferences pane displaying app information
3+
/// About preferences pane displaying app information with vibrant animations
44
struct AboutPreferencesPane: View {
55

66
// MARK: - Properties
@@ -12,47 +12,70 @@ struct AboutPreferencesPane: View {
1212
return "Version \(version) (Build \(build))"
1313
}()
1414

15+
@State private var isHovering = false
16+
@State private var rotationDegrees: Double = 0
17+
1518
// MARK: - Views
1619

1720
var body: some View {
18-
ScrollView {
19-
VStack(spacing: 24) {
20-
appInfoSection
21+
ZStack {
22+
animatedGradientBackground
23+
24+
ScrollView {
25+
VStack(spacing: 24) {
26+
appInfoSection
2127

22-
Spacer()
28+
Spacer()
29+
}
30+
.padding()
2331
}
24-
.padding()
2532
}
2633
}
34+
}
2735

28-
// MARK: - Subviews
29-
30-
private var appInfoSection: some View {
31-
VStack(spacing: 12) {
32-
// App Icon
33-
if let appIcon = NSImage(named: "AppIcon") {
34-
Image(nsImage: appIcon)
35-
.resizable()
36-
.frame(width: 128, height: 128)
37-
.cornerRadius(24)
38-
.shadow(color: .black.opacity(0.2), radius: 10, x: 0, y: 5)
39-
} else {
40-
// Fallback icon
41-
Image(systemName: "flag.circle.fill")
42-
.resizable()
43-
.frame(width: 128, height: 128)
44-
.foregroundColor(.accentColor)
36+
// MARK: - Animated Gradient Background
37+
private extension AboutPreferencesPane {
38+
39+
var animatedGradientBackground: some View {
40+
TimelineView(.animation(minimumInterval: 1.0 / 30.0)) { timeline in
41+
let elapsed = timeline.date.timeIntervalSinceReferenceDate
42+
let angle = Angle.degrees(elapsed.truncatingRemainder(dividingBy: 360) * 8)
43+
44+
AngularGradient(
45+
colors: [
46+
Color.purple.opacity(0.3),
47+
Color.blue.opacity(0.25),
48+
Color.teal.opacity(0.3),
49+
Color.pink.opacity(0.25),
50+
Color.purple.opacity(0.3)
51+
],
52+
center: .center,
53+
angle: angle
54+
)
55+
.blur(radius: 60)
56+
.ignoresSafeArea()
57+
}
58+
}
59+
}
60+
61+
// MARK: - App Info Section
62+
private extension AboutPreferencesPane {
63+
64+
var appInfoSection: some View {
65+
VStack(spacing: 16) {
66+
// App Icon with orbiting flags
67+
ZStack {
68+
orbitingFlagsView
69+
70+
interactiveAppIcon
4571
}
72+
.frame(width: 200, height: 200)
4673

4774
// App Name
4875
Text(appName)
49-
.font(.system(size: 32, weight: .bold))
76+
.font(.system(size: 32, weight: .bold, design: .rounded))
5077
.foregroundColor(.primary)
51-
52-
// Version
53-
Text(appVersion)
54-
.font(.callout)
55-
.foregroundColor(.secondary)
78+
.frame(maxWidth: .infinity, alignment: .center)
5679

5780
// Tagline
5881
Text("Beautiful keyboard layout switching for macOS")
@@ -61,40 +84,127 @@ struct AboutPreferencesPane: View {
6184
.multilineTextAlignment(.center)
6285
.padding(.horizontal, 40)
6386

87+
// Version — keycap style
88+
Text(appVersion)
89+
.font(.system(size: 11, weight: .medium, design: .rounded))
90+
.foregroundColor(.secondary)
91+
6492
// Links
65-
HStack(spacing: 20) {
66-
Button {
67-
if let url = URL(string: "https://bohdan-ios.github.io/languageflag-website/") {
68-
NSWorkspace.shared.open(url)
69-
}
70-
} label: {
71-
HStack(spacing: 4) {
72-
Image(systemName: "link.circle")
73-
Text("Website")
74-
}
93+
linksSection
94+
}
95+
.padding(.top, 20)
96+
}
97+
}
98+
99+
// MARK: - Orbiting Flags
100+
private extension AboutPreferencesPane {
101+
102+
/// Flags that orbit around the app icon in a slow circle
103+
var orbitingFlagsView: some View {
104+
let flags = ["🇺🇸", "🇫🇷", "🇩🇪", "🇯🇵", "🇰🇷", "🇺🇦", "🇪🇸", "🇧🇷"]
105+
let radius: CGFloat = 82
106+
107+
return TimelineView(.animation(minimumInterval: 1.0 / 30.0)) { timeline in
108+
let elapsed = timeline.date.timeIntervalSinceReferenceDate
109+
let baseAngle = elapsed.truncatingRemainder(dividingBy: 360) * 12 // degrees per second
110+
111+
Canvas { context, size in
112+
let center = CGPoint(x: size.width / 2, y: size.height / 2)
113+
114+
for (index, flag) in flags.enumerated() {
115+
let offsetAngle = baseAngle + Double(index) * (360.0 / Double(flags.count))
116+
let radians = offsetAngle * .pi / 180
117+
118+
let x = center.x + radius * cos(radians)
119+
let y = center.y + radius * sin(radians)
120+
121+
let text = Text(flag).font(.system(size: 20))
122+
context.opacity = 0.7
123+
context.draw(
124+
context.resolve(text),
125+
at: CGPoint(x: x, y: y),
126+
anchor: .center
127+
)
75128
}
76-
.buttonStyle(.link)
77-
78-
Button {
79-
if let url = URL(string: "https://github.com/bohdan-ios/LanguageFlag") {
80-
NSWorkspace.shared.open(url)
81-
}
82-
} label: {
83-
HStack(spacing: 4) {
84-
Image(systemName: "chevron.left.forwardslash.chevron.right")
85-
Text("Source")
86-
}
129+
}
130+
}
131+
}
132+
}
133+
134+
// MARK: - Interactive App Icon
135+
private extension AboutPreferencesPane {
136+
137+
var interactiveAppIcon: some View {
138+
ZStack {
139+
// Glow ring behind icon
140+
Circle()
141+
.fill(Color.accentColor.opacity(isHovering ? 0.4 : 0.15))
142+
.frame(width: 140, height: 140)
143+
.blur(radius: isHovering ? 20 : 12)
144+
145+
Group {
146+
if let appIcon = NSImage(named: "AppIcon") {
147+
Image(nsImage: appIcon)
148+
.resizable()
149+
.frame(width: 120, height: 120)
150+
.cornerRadius(24)
151+
.shadow(color: .black.opacity(0.25), radius: 12, x: 0, y: 6)
152+
} else {
153+
Image(systemName: "flag.circle.fill")
154+
.resizable()
155+
.frame(width: 120, height: 120)
156+
.foregroundColor(.accentColor)
87157
}
88-
.buttonStyle(.link)
89158
}
90-
.font(.caption)
159+
.rotation3DEffect(.degrees(rotationDegrees), axis: (x: 0, y: 1, z: 0))
160+
.scaleEffect(isHovering ? 1.08 : 1.0)
161+
.animation(.spring(response: 0.4, dampingFraction: 0.6), value: isHovering)
162+
}
163+
.onHover { hovering in
164+
isHovering = hovering
165+
}
166+
.onTapGesture {
167+
withAnimation(.spring(response: 0.6, dampingFraction: 0.5)) {
168+
rotationDegrees += 360
169+
}
91170
}
92-
.padding(.top, 20)
93171
}
94172
}
95173

96-
// MARK: - Preview
174+
// MARK: - Links
175+
private extension AboutPreferencesPane {
97176

177+
var linksSection: some View {
178+
HStack(spacing: 20) {
179+
Button {
180+
if let url = URL(string: "https://bohdan-ios.github.io/languageflag-website/") {
181+
NSWorkspace.shared.open(url)
182+
}
183+
} label: {
184+
HStack(spacing: 4) {
185+
Image(systemName: "link.circle")
186+
Text("Website")
187+
}
188+
}
189+
.buttonStyle(.link)
190+
191+
Button {
192+
if let url = URL(string: "https://github.com/bohdan-ios/LanguageFlag") {
193+
NSWorkspace.shared.open(url)
194+
}
195+
} label: {
196+
HStack(spacing: 4) {
197+
Image(systemName: "chevron.left.forwardslash.chevron.right")
198+
Text("Source")
199+
}
200+
}
201+
.buttonStyle(.link)
202+
}
203+
.font(.caption)
204+
}
205+
}
206+
207+
// MARK: - Preview
98208
struct AboutPreferencesPane_Previews: PreviewProvider {
99209

100210
static var previews: some View {

LanguageFlag/Stories/Preferences/PreferencesView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ struct PreferencesView: View {
1919
.tag(pane)
2020
}
2121
}
22-
.frame(width: 809, height: 500)
22+
.padding(.top, 8)
23+
.frame(width: 817, height: 505)
2324
}
2425

2526
@ViewBuilder

0 commit comments

Comments
 (0)