Last active
April 2, 2022 05:57
-
-
Save frboulais/7815a6206e45343c1fbd18ac04053ddf to your computer and use it in GitHub Desktop.
🔥 Help Center [Firestore x Combine x SwiftUI x Codable]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct HelpdeskArticle: Codable { | |
@DocumentID var id: String? | |
let title: String | |
let content: String | |
let locale: String | |
} | |
struct HelpdeskView: View { | |
@StateObject var viewModel = HelpdeskViewModel() | |
var body: some View { | |
Group { | |
if let sections = viewModel.sections { | |
if sections.isEmpty { | |
Text("No article available for your language") | |
.foregroundStyle(.secondary) | |
} else { | |
List { | |
ForEach(sections) { section in | |
Section { | |
if viewModel.isSectionExpanded(section) { | |
Text(section.content) | |
} | |
} header: { | |
Button { | |
viewModel.toggleSection(section) | |
} label: { | |
HStack { | |
Text(section.title) | |
.font(.headline) | |
Spacer() | |
Image(systemName: "chevron.right") | |
.imageScale(.medium) | |
.rotationEffect(.degrees(viewModel.isSectionExpanded(section) ? 90 : 0)) | |
} | |
} | |
} | |
.foregroundColor(.asset(.midnight)) | |
} | |
.listRowSeparator(.hidden) | |
} | |
.listStyle(.plain) | |
} | |
} else if let error = viewModel.error { | |
Text(error.localizedDescription) | |
.foregroundStyle(.secondary) | |
} else { | |
ProgressView() | |
} | |
} | |
.task { | |
await viewModel.fetchArticles() | |
} | |
.navigationTitle("HELP CENTER") | |
} | |
} | |
class HelpdeskViewModel: ObservableObject { | |
struct Section: Equatable, Identifiable { | |
let id: String? | |
let title: String | |
let content: AttributedString | |
init(article: HelpdeskArticle) throws { | |
self.id = article.id | |
self.title = article.title | |
self.content = try .init(markdown: article.content) | |
} | |
} | |
@Injected var firestoreManager: FirestoreManager | |
@Published var sections: [Section]? | |
@Published var error: Error? | |
@Published var expandedSection: Section? | |
@MainActor func fetchArticles() async { | |
do { | |
guard let languageCode = Locale.current.languageCode else { | |
return | |
} | |
sections = try await firestoreManager.helpdesk(for: languageCode) | |
.map { try .init(article: $0) } | |
} catch { | |
self.error = error | |
} | |
} | |
func isSectionExpanded(_ section: Section) -> Bool { | |
section == expandedSection | |
} | |
func toggleSection(_ section: Section) { | |
withAnimation { | |
expandedSection = isSectionExpanded(section) ? nil : section | |
} | |
} | |
} | |
protocol FirestoreManagerProtocol { | |
func helpdesk(for locale: String) async throws -> [HelpdeskArticle] | |
} | |
class FirestoreManager: FirestoreManagerProtocol | |
func helpdesk(for locale: String) async throws -> [HelpdeskArticle] { | |
try await firestore.collection(.helpdesk) | |
.whereField(.locale, isEqualTo: locale) | |
.getDocuments() | |
.documents.map { try $0.data(as: HelpdeskArticle.self) } | |
} | |
} | |
fileprivate extension Firestore { | |
enum Collection: String { | |
case users, helpdesk | |
} | |
func collection(_ collection: Collection) -> CollectionReference { | |
self.collection(collection.rawValue) | |
} | |
} | |
fileprivate extension DocumentReference { | |
enum Collection: String { | |
case entries | |
} | |
func collection(_ collection: Collection) -> CollectionReference { | |
self.collection(collection.rawValue) | |
} | |
} | |
fileprivate extension Query { | |
enum Field: String { | |
case locale | |
} | |
func whereField(_ field: Field, isEqualTo value: Any) -> Query { | |
self.whereField(field.rawValue, isEqualTo: value) | |
} | |
} | |
extension Resolver { | |
public static func registerFirestoreManager() { | |
register { FirestoreManager() } | |
.implements(FirestoreManagerProtocol.self) | |
.scope(.application) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment