Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Friendly/Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ struct ContentView: View {
routeToSignUp: viewModel.routeToSignUp,
addFriend: $viewModel.addFriend,
)
case .qrAddFriend:
ScanToUseAppView(isBlocked: true) { viewModel.onAddFriendWithQr() }
}
}
.animation(.easeInOut(duration: 0.3), value: viewModel.destination)
Expand Down
12 changes: 11 additions & 1 deletion Friendly/Sources/ContentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ class ContentViewModel {
func appear() {
do {
if try storage.hasAuthorization() {
destination = .main
if try storage.getHasFriend() {
destination = .main
} else {
destination = .qrAddFriend
}
} else {
destination = .signUp
}
Expand All @@ -21,6 +25,11 @@ class ContentViewModel {
}

func onSignUp() {
destination = .qrAddFriend
}

func onAddFriendWithQr() {
try? storage.addFriend()
destination = .main
}

Expand All @@ -36,5 +45,6 @@ class ContentViewModel {
case empty
case signUp
case main
case qrAddFriend
}
}
80 changes: 80 additions & 0 deletions Friendly/Sources/Deeplink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,83 @@ enum Deeplink {
return nil
}
}

extension Deeplink {
// friendly://add/<id>/<token>
private static let friendlyRegex: Regex<(Substring, Substring, Substring)> =
try! Regex(#"^friendly://add/([^/]+)/(.+)$"#)

// add/<id>/<token>
private static let referenceRegex: Regex<(Substring, Substring, Substring)> =
try! Regex(#"^add/([^/]+)/(.+)$"#)

static func parseOf(url: URL) -> Deeplink? {
if let deeplink = parseFriendly(url: url) { return deeplink }
if let deeplink = parseReferenceURL(url: url) { return deeplink }
return nil
}
}

private extension Deeplink {
static func parseFriendly(url: URL) -> Deeplink? {
guard url.scheme?.lowercased() == "friendly" else { return nil }

let string = url.absoluteString
guard let match = try? friendlyRegex.wholeMatch(in: string) else { return nil }

let rawId = String(match.output.1)
let rawToken = String(match.output.2)
return makeAddFriend(rawId: rawId, rawToken: rawToken)
}

static func parseReferenceURL(url: URL) -> Deeplink? {
if let reference = url.queryValue("reference"),
let deeplink = parseReference(reference) {
return deeplink
}

if let fragment = url.fragment,
let reference = extractReference(from: fragment),
let deeplink = parseReference(reference) {
return deeplink
}

return nil
}

static func parseReference(_ reference: String) -> Deeplink? {
let decoded = reference.removingPercentEncoding ?? reference
guard let match = try? referenceRegex.wholeMatch(in: decoded) else { return nil }

let rawId = String(match.output.1)
let rawToken = String(match.output.2)
return makeAddFriend(rawId: rawId, rawToken: rawToken)
}

static func extractReference(from fragment: String) -> String? {
let decoded = fragment.removingPercentEncoding ?? fragment
guard let range = decoded.range(of: "reference=") else { return nil }

let tail = decoded[range.upperBound...]
if let amp = tail.firstIndex(of: "&") {
return String(tail[..<amp])
}
return String(tail)
}

static func makeAddFriend(rawId: String, rawToken: String) -> Deeplink? {
guard let idInt64 = Int64(rawId) else { return nil }
let id = UserId(idInt64)
guard let token = try? FriendToken(rawToken) else { return nil }
return .addFriend(id: id, token: token)
}
}

private extension URL {
func queryValue(_ name: String) -> String? {
URLComponents(url: self, resolvingAgainstBaseURL: false)?
.queryItems?
.first(where: { $0.name == name })?
.value
}
}
Loading