141 lines
4.6 KiB
Swift
141 lines
4.6 KiB
Swift
//
|
||
// PatternService.swift
|
||
// LifeGameApp
|
||
//
|
||
// Created by Yusuke Hosonuma on 2020/09/12.
|
||
//
|
||
|
||
import Foundation
|
||
import Combine
|
||
import FirebaseFirestore
|
||
|
||
// Note:
|
||
//
|
||
// This service is designed by fail-safe,
|
||
// therefore some method call is return empty (publisher) that login is neeeded.
|
||
|
||
public struct PatternURL {
|
||
public let url: URL
|
||
public let stared: Bool
|
||
}
|
||
|
||
final public class PatternService {
|
||
public static let shared = PatternService()
|
||
|
||
private let authentication: Authentication = .shared
|
||
private let patternIDRepository: FirestorePatternIndexRepository = .shared
|
||
private var staredRepository: FirestoreStaredRepository? { authentication.repositories?.stared }
|
||
private var historyRepository: FirestoreHistoryRepository? { authentication.repositories?.history }
|
||
|
||
public func patternURLs(by type: String? = nil) -> AnyPublisher<[PatternURL], Never> {
|
||
patternIDRepository
|
||
.all
|
||
.map { document -> [URL] in
|
||
if let type = type {
|
||
// TODO: カテゴリ単位でドキュメントを分けているので、そこから個別に取得したほうが高速(になるはず)
|
||
return document.data.filter { $0.patternType == type }.map(\.jsonURL)
|
||
} else {
|
||
return document.data.map(\.jsonURL)
|
||
}
|
||
}
|
||
.combineLatest(listenStaredPatternURLs())
|
||
.map { allURLs, staredURLs in
|
||
allURLs.map {
|
||
PatternURL(url: $0, stared: staredURLs.contains($0))
|
||
}
|
||
}
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
public func fetch(from url: URL) -> AnyPublisher<PatternItem?, Never> {
|
||
let patternPublisher = URLSession.shared
|
||
.dataTaskPublisher(for: url)
|
||
.retry(3)
|
||
.map { (data, response) -> Data in
|
||
data
|
||
}
|
||
.decode(type: LifeWikiPattern.self, decoder: JSONDecoder())
|
||
|
||
return Publishers.Zip(patternPublisher, staredPatternIDs().setFailureType(to: Error.self))
|
||
.map { pattern, staredIds in
|
||
PatternItem(
|
||
patternID: pattern.id,
|
||
title: pattern.title,
|
||
board: pattern.makeBoard(),
|
||
stared: staredIds.contains(pattern.id)
|
||
)
|
||
}
|
||
.replaceError(with: nil) // TODO: エラーを投げる設計に変えたほうがいい
|
||
.receive(on: RunLoop.main)
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
// MARK: - Star
|
||
|
||
public func staredPatternURLs(by type: String? = nil) -> AnyPublisher<[PatternURL], Never> {
|
||
guard let staredRepository = staredRepository else {
|
||
return Just([]).eraseToAnyPublisher()
|
||
}
|
||
|
||
return staredRepository
|
||
.all()
|
||
.map { urls in
|
||
urls.map { PatternURL(url: $0.jsonURL, stared: true) }
|
||
}
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
public func listenStaredPatternURLs() -> AnyPublisher<[URL], Never> {
|
||
guard let staredRepository = staredRepository else {
|
||
return Just([]).eraseToAnyPublisher()
|
||
}
|
||
|
||
return staredRepository
|
||
.publisher
|
||
.map { $0.map(\.jsonURL) }
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
public func toggleStar(item: PatternItem) {
|
||
guard let staredRepository = staredRepository else { return }
|
||
|
||
if item.stared {
|
||
staredRepository.delete(by: item.patternID)
|
||
} else {
|
||
staredRepository.setData(StaredDocument(patternID: item.patternID))
|
||
}
|
||
}
|
||
|
||
// MARK: - History
|
||
|
||
public func recordHistory(patternID: String) {
|
||
guard let historyRepository = historyRepository else { return }
|
||
|
||
historyRepository.setData(by: patternID, document: HistoryDocument())
|
||
}
|
||
|
||
public func listenHistoryPatternURLs() -> AnyPublisher<[URL], Never> {
|
||
guard let historyRepository = historyRepository else {
|
||
return Just([]).eraseToAnyPublisher()
|
||
}
|
||
|
||
return historyRepository
|
||
.publisher
|
||
.map { $0.map(\.jsonURL) }
|
||
.eraseToAnyPublisher()
|
||
}
|
||
|
||
// MARK: - Private
|
||
|
||
private func staredPatternIDs() -> AnyPublisher<[String], Never> {
|
||
guard let staredRepository = staredRepository else {
|
||
return Just([]).eraseToAnyPublisher()
|
||
}
|
||
|
||
return staredRepository // TODO: たぶん登録順でソートさせたくなるはず(あるいはタイトル?)
|
||
.all()
|
||
.map { $0.map(\.patternID) }
|
||
.eraseToAnyPublisher()
|
||
}
|
||
}
|