Skip to content

Commit a50ff62

Browse files
committedNov 2, 2024
feat: UI refactor for landing page
1 parent 5967d03 commit a50ff62

File tree

10 files changed

+177
-19
lines changed

10 files changed

+177
-19
lines changed
 

‎.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

‎FollowUI/Package.swift

+18-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,29 @@ let package = Package(
1010
// Products define the executables and libraries a package produces, making them visible to other packages.
1111
.library(
1212
name: "FollowUI",
13-
targets: ["FollowUI"]),
13+
targets: ["FollowUI"])
14+
],
15+
dependencies: [
16+
.package(name: "FollowAPI", path: "../FollowAPI"),
17+
.package(url: "https://github.com/onevcat/Kingfisher", exact: "8.1.0"),
18+
.package(url: "https://github.com/malcommac/SwiftDate", exact: "7.0.0"),
19+
.package(url: "https://github.com/scinfu/SwiftSoup", exact: "2.7.5"),
20+
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui", exact: "2.4.1"),
21+
.package(url: "https://github.com/LiveUI/Awesome", exact: "2.4.0")
1422
],
15-
dependencies: [.package(name: "FollowAPI", path: "../FollowAPI"), .package(url: "https://github.com/onevcat/Kingfisher", exact: "8.1.0"), .package(url: "https://github.com/malcommac/SwiftDate", exact: "7.0.0"), .package(url: "https://github.com/scinfu/SwiftSoup", exact: "2.7.5"), .package(url: "https://github.com/gonzalezreal/swift-markdown-ui", exact: "2.4.1")],
1623
targets: [
1724
// Targets are the basic building blocks of a package, defining a module or a test suite.
1825
// Targets can depend on other targets in this package and products from dependencies.
1926
.target(
20-
name: "FollowUI", dependencies: [.product(name: "FollowAPI", package: "FollowAPI"), .product(name: "Kingfisher", package: "Kingfisher"), .product(name: "SwiftDate", package: "SwiftDate"), .product(name: "SwiftSoup", package: "SwiftSoup"), .product(name: "MarkdownUI", package: "swift-markdown-ui")]),
27+
name: "FollowUI",
28+
dependencies: [
29+
.product(name: "FollowAPI", package: "FollowAPI"),
30+
.product(name: "Kingfisher", package: "Kingfisher"),
31+
.product(name: "SwiftDate", package: "SwiftDate"),
32+
.product(name: "SwiftSoup", package: "SwiftSoup"),
33+
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
34+
.product(name: "Awesome", package: "Awesome")
35+
]),
2136
.testTarget(
2237
name: "FollowUITests",
2338
dependencies: ["FollowUI"]

‎FollowUI/Sources/FollowUI/LandingView.swift

+122-15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import AuthenticationServices
9+
import Awesome
910
import FollowAPI
1011
import Kingfisher
1112
import SwiftUI
@@ -26,7 +27,8 @@ class AuthenticationHandler: NSObject, ObservableObject, @unchecked Sendable {
2627
func startAuthentication() {
2728
guard let url = URL(string: "https://app.follow.is/login?provider=github") else { return }
2829

29-
webAuthSession = ASWebAuthenticationSession(url: url, callbackURLScheme: "follow") { callbackURL, error in
30+
webAuthSession = ASWebAuthenticationSession(url: url, callbackURLScheme: "follow") {
31+
callbackURL, error in
3032
guard error == nil, let successURL = callbackURL else {
3133
print("Authentication failed: \(error?.localizedDescription ?? "Unknown error")")
3234
return
@@ -42,7 +44,8 @@ class AuthenticationHandler: NSObject, ObservableObject, @unchecked Sendable {
4244

4345
private func handleCallback(url: URL) {
4446
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
45-
let queryItems = components.queryItems else { return }
47+
let queryItems = components.queryItems
48+
else { return }
4649

4750
if let token = queryItems.first(where: { $0.name == "token" })?.value {
4851
Task {
@@ -63,7 +66,7 @@ class AuthenticationHandler: NSObject, ObservableObject, @unchecked Sendable {
6366
}
6467
}
6568
}
66-
69+
6770
private func setSessionToken(_ token: String) {
6871
do {
6972
let data = try JSONEncoder().encode(token)
@@ -94,7 +97,9 @@ class AuthenticationHandler: NSObject, ObservableObject, @unchecked Sendable {
9497
private func loadSessionData() async {
9598
if let sessionTokenData = KeychainWrapper.load(forKey: "sessionToken") {
9699
do {
97-
let sessionToken: String = try JSONDecoder().decode(String.self, from: sessionTokenData)
100+
let sessionToken: String = try JSONDecoder().decode(
101+
String.self, from: sessionTokenData
102+
)
98103
NetworkManager.shared.setSessionToken(sessionToken)
99104
let csrfToken = try await authService.getCsrfToken().csrfToken
100105
NetworkManager.shared.setCSRToken(csrfToken)
@@ -109,8 +114,8 @@ class AuthenticationHandler: NSObject, ObservableObject, @unchecked Sendable {
109114

110115
func logout() {
111116
isAuthenticated = false
112-
KeychainWrapper.delete(forKey: "sessionData")
113-
KeychainWrapper.delete(forKey: "sessionToken")
117+
let _ = KeychainWrapper.delete(forKey: "sessionData")
118+
let _ = KeychainWrapper.delete(forKey: "sessionToken")
114119
NetworkManager.shared.clearTokens()
115120
}
116121
}
@@ -121,9 +126,29 @@ extension AuthenticationHandler: ASWebAuthenticationPresentationContextProviding
121126
}
122127
}
123128

129+
extension Color {
130+
static let redGradientStart = Color(
131+
light: .init(red: 252 / 255, green: 165 / 255, blue: 165 / 255), // red-300
132+
dark: .init(red: 239 / 255, green: 68 / 255, blue: 68 / 255)
133+
) // red-500
134+
static let orangeGradientEnd = Color(
135+
light: .init(red: 253 / 255, green: 186 / 255, blue: 116 / 255), // orange-300
136+
dark: .init(red: 249 / 255, green: 115 / 255, blue: 22 / 255)
137+
) // orange-500
138+
}
139+
124140
struct LandingView: View {
125141
@StateObject private var authHandler = AuthenticationHandler()
126142
@State private var showMainView = false
143+
@State private var logoOffset: CGFloat = 50
144+
@State private var logoOpacity: CGFloat = 0
145+
@State private var titleOffset: CGFloat = 50
146+
@State private var titleOpacity: CGFloat = 0
147+
@State private var subtitleOffset: CGFloat = 50
148+
@State private var subtitleOpacity: CGFloat = 0
149+
@State private var buttonsOffset: CGFloat = 50
150+
@State private var buttonsOpacity: CGFloat = 0
151+
@State private var animateGradient: Bool = false
127152

128153
var body: some View {
129154
Group {
@@ -146,27 +171,109 @@ struct LandingView: View {
146171
private var contentView: some View {
147172
if let isAuthenticated = authHandler.isAuthenticated {
148173
VStack {
149-
KFImage.url(URL(string: "https://github.com/RSSNext/follow/assets/41265413/c6c02ad5-cddc-46f5-8420-a47afe1c82fe")!)
150-
.resizable()
151-
.frame(width: 80, height: 80)
174+
Spacer()
175+
HStack {
176+
Image("FollowIcon")
177+
.resizable()
178+
.frame(width: 40, height: 40)
179+
Text("Follow")
180+
.font(.custom("SNProVF-Bold", size: 28))
181+
}
182+
.offset(y: logoOffset)
183+
Spacer()
184+
.frame(height: 20)
185+
HStack(spacing: 0) {
186+
Text("Next-Gen")
187+
.background(
188+
LinearGradient(
189+
colors: [
190+
.redGradientStart,
191+
.orangeGradientEnd,
192+
],
193+
startPoint: .leading,
194+
endPoint: .trailing
195+
)
196+
.cornerRadius(8)
197+
.scaleEffect(x: animateGradient ? 1 : 0, y: 1, anchor: .leading)
198+
.animation(.snappy(duration: 0.25).delay(0.6), value: animateGradient)
199+
)
200+
.onAppear {
201+
animateGradient = true
202+
}
203+
Text(" Information Browser")
204+
}
205+
.font(.custom("SNProVF-Bold", size: 20))
206+
.offset(y: titleOffset)
207+
.opacity(titleOpacity)
152208

209+
Spacer()
153210
if isAuthenticated {
154211
Text("Redirecting...")
212+
.offset(y: buttonsOffset)
213+
.opacity(buttonsOpacity)
155214
} else {
156-
Button(action: {
157-
Task {
158-
await authHandler.startAuthentication()
215+
VStack(spacing: 10) {
216+
Button(action: {
217+
authHandler.startAuthentication()
218+
}) {
219+
HStack {
220+
Spacer()
221+
Awesome.Brand.github.image
222+
.foregroundColor(.white)
223+
Text("Continue with Github")
224+
.bold()
225+
Spacer()
226+
}
227+
.padding()
228+
.background(Color.black)
229+
.foregroundColor(.white)
230+
.cornerRadius(8)
159231
}
160-
}) {
161-
Text("Continue with Github")
232+
Button(action: {
233+
Task {
234+
// TODO: Sign in with Google
235+
}
236+
}) {
237+
HStack {
238+
Spacer()
239+
Awesome.Brand.google.image
240+
.foregroundColor(.white)
241+
Text("Continue with Google")
242+
.bold()
243+
Spacer()
244+
}
162245
.padding()
163-
.background(Color.blue)
246+
.background(Color(red: 59 / 255, green: 130 / 255, blue: 246 / 255))
164247
.foregroundColor(.white)
165248
.cornerRadius(8)
249+
}
166250
}
251+
.offset(y: buttonsOffset)
252+
.opacity(buttonsOpacity)
167253
}
168254
}
169255
.padding()
256+
.onAppear {
257+
withAnimation(.snappy(duration: 0.5).delay(0.0)) {
258+
logoOffset = 0
259+
logoOpacity = 1
260+
}
261+
262+
withAnimation(.snappy(duration: 0.5).delay(0.2)) {
263+
titleOffset = 0
264+
titleOpacity = 1
265+
}
266+
267+
withAnimation(.snappy(duration: 0.5).delay(0.4)) {
268+
subtitleOffset = 0
269+
subtitleOpacity = 1
270+
}
271+
272+
withAnimation(.snappy(duration: 0.5).delay(0.6)) {
273+
buttonsOffset = 0
274+
buttonsOpacity = 1
275+
}
276+
}
170277
} else {
171278
ProgressView()
172279
}

‎swift-follow.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"originHash" : "b130d4edf7fb0176bca2e8d70398a8a0bcc25144496846e752fcd367aac40855",
2+
"originHash" : "91140ebe1826dcfd8d8bc983d1fc54bb4fbd2cb0461c95fbae7f24ec80106613",
33
"pins" : [
44
{
55
"identity" : "alamofire",
@@ -10,6 +10,15 @@
1010
"version" : "5.10.0"
1111
}
1212
},
13+
{
14+
"identity" : "awesome",
15+
"kind" : "remoteSourceControl",
16+
"location" : "https://github.com/LiveUI/Awesome",
17+
"state" : {
18+
"revision" : "40c68d0ca2b143c1118e636518706a4b8332777b",
19+
"version" : "2.4.0"
20+
}
21+
},
1322
{
1423
"identity" : "kingfisher",
1524
"kind" : "remoteSourceControl",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "344728860-c6c02ad5-cddc-46f5-8420-a47afe1c82fe 6.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"filename" : "344728860-c6c02ad5-cddc-46f5-8420-a47afe1c82fe.png",
10+
"idiom" : "universal",
11+
"scale" : "2x"
12+
},
13+
{
14+
"idiom" : "universal",
15+
"scale" : "3x"
16+
}
17+
],
18+
"info" : {
19+
"author" : "xcode",
20+
"version" : 1
21+
}
22+
}

‎swift-follow/Info.plist

+4
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@
77
<key>NSAllowsArbitraryLoads</key>
88
<true/>
99
</dict>
10+
<key>UIAppFonts</key>
11+
<array>
12+
<string>SNPro.ttf</string>
13+
</array>
1014
</dict>
1115
</plist>

‎swift-follow/SNPro.ttf

441 KB
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.