mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-26 10:10:58 +00:00
Basic status fetching and rendering
This commit is contained in:
parent
f6568abad9
commit
b5017d4805
30 changed files with 2544 additions and 40 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"data" : [
|
||||||
|
{
|
||||||
|
"filename" : "timeline.json",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"universal-type-identifier" : "public.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -47,7 +47,9 @@ extension AppEnvironment {
|
||||||
static let development = AppEnvironment(
|
static let development = AppEnvironment(
|
||||||
session: Session(configuration: .stubbing),
|
session: Session(configuration: .stubbing),
|
||||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
||||||
keychainServiceType: MockKeychainService.self)
|
keychainServiceType: MockKeychainService.self,
|
||||||
|
userDefaults: MockUserDefaults(),
|
||||||
|
inMemoryContent: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension IdentitiesService {
|
extension IdentitiesService {
|
||||||
|
@ -110,4 +112,8 @@ extension NotificationTypesPreferencesViewModel {
|
||||||
static let development = NotificationTypesPreferencesViewModel(identityService: .development)
|
static let development = NotificationTypesPreferencesViewModel(identityService: .development)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension StatusesViewModel {
|
||||||
|
static let development = StatusesViewModel(statusListService: IdentityService.development.service(timeline: .home))
|
||||||
|
}
|
||||||
|
|
||||||
// swiftlint:enable force_try
|
// swiftlint:enable force_try
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extension TimelinesEndpoint: Stubbing {
|
||||||
|
func data(url: URL) -> Data? {
|
||||||
|
NSDataAsset(name: "TimelineJSON")!.data
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,5 +30,7 @@ extension Stubbing {
|
||||||
dataString(url: url)?.data(using: .utf8)
|
dataString(url: url)?.data(using: .utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dataString(url: URL) -> String? { nil }
|
||||||
|
|
||||||
func statusCode(url: URL) -> Int? { 200 }
|
func statusCode(url: URL) -> Int? { 200 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,34 @@
|
||||||
D052BBCB24D74C9300A80A7A /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */; };
|
D052BBCB24D74C9300A80A7A /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */; };
|
||||||
D052BBD124D750CA00A80A7A /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBCC24D750A100A80A7A /* AppEnvironment.swift */; };
|
D052BBD124D750CA00A80A7A /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBCC24D750A100A80A7A /* AppEnvironment.swift */; };
|
||||||
D052BBD224D750CB00A80A7A /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBCC24D750A100A80A7A /* AppEnvironment.swift */; };
|
D052BBD224D750CB00A80A7A /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBCC24D750A100A80A7A /* AppEnvironment.swift */; };
|
||||||
|
D05494E424EA3EF7008B00A5 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E324EA3EF7008B00A5 /* Tag.swift */; };
|
||||||
|
D05494E524EA3EF7008B00A5 /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E324EA3EF7008B00A5 /* Tag.swift */; };
|
||||||
|
D05494E724EA3F1A008B00A5 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E624EA3F1A008B00A5 /* Mention.swift */; };
|
||||||
|
D05494E824EA3F1A008B00A5 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E624EA3F1A008B00A5 /* Mention.swift */; };
|
||||||
|
D05494EA24EA3F54008B00A5 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E924EA3F54008B00A5 /* Attachment.swift */; };
|
||||||
|
D05494EB24EA3F54008B00A5 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494E924EA3F54008B00A5 /* Attachment.swift */; };
|
||||||
|
D05494ED24EA3FA9008B00A5 /* Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494EC24EA3FA9008B00A5 /* Poll.swift */; };
|
||||||
|
D05494EE24EA3FA9008B00A5 /* Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494EC24EA3FA9008B00A5 /* Poll.swift */; };
|
||||||
|
D05494F024EA3FE5008B00A5 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494EF24EA3FE5008B00A5 /* Card.swift */; };
|
||||||
|
D05494F124EA3FE5008B00A5 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494EF24EA3FE5008B00A5 /* Card.swift */; };
|
||||||
|
D05494F724EA49F7008B00A5 /* TimelinesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494F624EA49F7008B00A5 /* TimelinesEndpoint.swift */; };
|
||||||
|
D05494F824EA49F7008B00A5 /* TimelinesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494F624EA49F7008B00A5 /* TimelinesEndpoint.swift */; };
|
||||||
|
D05494FA24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */; };
|
||||||
|
D05494FB24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */; };
|
||||||
|
D054950124EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */; };
|
||||||
|
D054950224EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */; };
|
||||||
|
D054951224EB1041008B00A5 /* StatusListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951124EB1041008B00A5 /* StatusListService.swift */; };
|
||||||
|
D054951324EB1041008B00A5 /* StatusListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951124EB1041008B00A5 /* StatusListService.swift */; };
|
||||||
|
D054951524EB1053008B00A5 /* TimelineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951424EB1053008B00A5 /* TimelineService.swift */; };
|
||||||
|
D054951624EB1053008B00A5 /* TimelineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951424EB1053008B00A5 /* TimelineService.swift */; };
|
||||||
|
D054951B24EB2825008B00A5 /* TransientStatusCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951A24EB2825008B00A5 /* TransientStatusCollection.swift */; };
|
||||||
|
D054951C24EB2825008B00A5 /* TransientStatusCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D054951A24EB2825008B00A5 /* TransientStatusCollection.swift */; };
|
||||||
|
D057426724E9FE1D00839EBA /* ContentDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426624E9FE1D00839EBA /* ContentDatabase.swift */; };
|
||||||
|
D057426824E9FE1D00839EBA /* ContentDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426624E9FE1D00839EBA /* ContentDatabase.swift */; };
|
||||||
|
D057426A24EA32AC00839EBA /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426924EA32AC00839EBA /* Timeline.swift */; };
|
||||||
|
D057426B24EA32AC00839EBA /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426924EA32AC00839EBA /* Timeline.swift */; };
|
||||||
|
D057426D24EA339300839EBA /* ListTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426C24EA339300839EBA /* ListTimeline.swift */; };
|
||||||
|
D057426E24EA339300839EBA /* ListTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D057426C24EA339300839EBA /* ListTimeline.swift */; };
|
||||||
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; };
|
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; };
|
||||||
D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; };
|
D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; };
|
||||||
D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; };
|
D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D065F53A24D3B33A00741304 /* View+Extensions.swift */; };
|
||||||
|
@ -102,10 +130,10 @@
|
||||||
D0BEC93924C9632800E864C4 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93724C9632800E864C4 /* RootViewModel.swift */; };
|
D0BEC93924C9632800E864C4 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93724C9632800E864C4 /* RootViewModel.swift */; };
|
||||||
D0BEC93B24C96FD500E864C4 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93A24C96FD500E864C4 /* RootView.swift */; };
|
D0BEC93B24C96FD500E864C4 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93A24C96FD500E864C4 /* RootView.swift */; };
|
||||||
D0BEC93C24C96FD500E864C4 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93A24C96FD500E864C4 /* RootView.swift */; };
|
D0BEC93C24C96FD500E864C4 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93A24C96FD500E864C4 /* RootView.swift */; };
|
||||||
D0BEC94724CA22C400E864C4 /* TimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94624CA22C400E864C4 /* TimelineViewModel.swift */; };
|
D0BEC94724CA22C400E864C4 /* StatusesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94624CA22C400E864C4 /* StatusesViewModel.swift */; };
|
||||||
D0BEC94824CA22C400E864C4 /* TimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94624CA22C400E864C4 /* TimelineViewModel.swift */; };
|
D0BEC94824CA22C400E864C4 /* StatusesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94624CA22C400E864C4 /* StatusesViewModel.swift */; };
|
||||||
D0BEC94A24CA231200E864C4 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94924CA231200E864C4 /* TimelineView.swift */; };
|
D0BEC94A24CA231200E864C4 /* StatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94924CA231200E864C4 /* StatusesView.swift */; };
|
||||||
D0BEC94B24CA231200E864C4 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94924CA231200E864C4 /* TimelineView.swift */; };
|
D0BEC94B24CA231200E864C4 /* StatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC94924CA231200E864C4 /* StatusesView.swift */; };
|
||||||
D0C963FB24CC359D003BD330 /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FA24CC359D003BD330 /* AlertItem.swift */; };
|
D0C963FB24CC359D003BD330 /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FA24CC359D003BD330 /* AlertItem.swift */; };
|
||||||
D0C963FC24CC359D003BD330 /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FA24CC359D003BD330 /* AlertItem.swift */; };
|
D0C963FC24CC359D003BD330 /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FA24CC359D003BD330 /* AlertItem.swift */; };
|
||||||
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */; };
|
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */; };
|
||||||
|
@ -258,6 +286,20 @@
|
||||||
D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.swift; sourceTree = "<group>"; };
|
D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
|
D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
|
||||||
D052BBCC24D750A100A80A7A /* AppEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = "<group>"; };
|
D052BBCC24D750A100A80A7A /* AppEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = "<group>"; };
|
||||||
|
D05494E324EA3EF7008B00A5 /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
|
||||||
|
D05494E624EA3F1A008B00A5 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
|
||||||
|
D05494E924EA3F54008B00A5 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
|
||||||
|
D05494EC24EA3FA9008B00A5 /* Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poll.swift; sourceTree = "<group>"; };
|
||||||
|
D05494EF24EA3FE5008B00A5 /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = "<group>"; };
|
||||||
|
D05494F624EA49F7008B00A5 /* TimelinesEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesEndpoint.swift; sourceTree = "<group>"; };
|
||||||
|
D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelinesEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
|
||||||
|
D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DevelopmentAssets.xcassets; sourceTree = "<group>"; };
|
||||||
|
D054951124EB1041008B00A5 /* StatusListService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListService.swift; sourceTree = "<group>"; };
|
||||||
|
D054951424EB1053008B00A5 /* TimelineService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineService.swift; sourceTree = "<group>"; };
|
||||||
|
D054951A24EB2825008B00A5 /* TransientStatusCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStatusCollection.swift; sourceTree = "<group>"; };
|
||||||
|
D057426624E9FE1D00839EBA /* ContentDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentDatabase.swift; sourceTree = "<group>"; };
|
||||||
|
D057426924EA32AC00839EBA /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
||||||
|
D057426C24EA339300839EBA /* ListTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimeline.swift; sourceTree = "<group>"; };
|
||||||
D065F53A24D3B33A00741304 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
D065F53A24D3B33A00741304 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -278,8 +320,8 @@
|
||||||
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = "<group>"; };
|
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0BEC93724C9632800E864C4 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; };
|
D0BEC93724C9632800E864C4 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; };
|
||||||
D0BEC93A24C96FD500E864C4 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
D0BEC93A24C96FD500E864C4 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||||
D0BEC94624CA22C400E864C4 /* TimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewModel.swift; sourceTree = "<group>"; };
|
D0BEC94624CA22C400E864C4 /* StatusesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusesViewModel.swift; sourceTree = "<group>"; };
|
||||||
D0BEC94924CA231200E864C4 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
D0BEC94924CA231200E864C4 /* StatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusesView.swift; sourceTree = "<group>"; };
|
||||||
D0C963FA24CC359D003BD330 /* AlertItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = "<group>"; };
|
D0C963FA24CC359D003BD330 /* AlertItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = "<group>"; };
|
||||||
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Extensions.swift"; sourceTree = "<group>"; };
|
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0CD847224DBDEC700CF380C /* MastodonPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPreferences.swift; sourceTree = "<group>"; };
|
D0CD847224DBDEC700CF380C /* MastodonPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPreferences.swift; sourceTree = "<group>"; };
|
||||||
|
@ -409,6 +451,7 @@
|
||||||
D019E6E024DF72E700697C7D /* InstanceEndpoint.swift */,
|
D019E6E024DF72E700697C7D /* InstanceEndpoint.swift */,
|
||||||
D019E6DD24DF72E700697C7D /* PreferencesEndpoint.swift */,
|
D019E6DD24DF72E700697C7D /* PreferencesEndpoint.swift */,
|
||||||
D0EC8DED24E2704D00A08489 /* PushSubscriptionEndpoint.swift */,
|
D0EC8DED24E2704D00A08489 /* PushSubscriptionEndpoint.swift */,
|
||||||
|
D05494F624EA49F7008B00A5 /* TimelinesEndpoint.swift */,
|
||||||
);
|
);
|
||||||
path = Endpoints;
|
path = Endpoints;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -418,6 +461,7 @@
|
||||||
children = (
|
children = (
|
||||||
D019E6EF24DF7C2F00697C7D /* DatabaseError.swift */,
|
D019E6EF24DF7C2F00697C7D /* DatabaseError.swift */,
|
||||||
D019E6EC24DF7BF300697C7D /* IdentityDatabase.swift */,
|
D019E6EC24DF7BF300697C7D /* IdentityDatabase.swift */,
|
||||||
|
D057426624E9FE1D00839EBA /* ContentDatabase.swift */,
|
||||||
);
|
);
|
||||||
path = Databases;
|
path = Databases;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -425,12 +469,13 @@
|
||||||
D019E6F224DF7C9E00697C7D /* Services */ = {
|
D019E6F224DF7C9E00697C7D /* Services */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D054951024EB101F008B00A5 /* Status List Services */,
|
||||||
D0EC8DCD24DFB64200A08489 /* AuthenticationService.swift */,
|
D0EC8DCD24DFB64200A08489 /* AuthenticationService.swift */,
|
||||||
D0EC8DCA24DFA06700A08489 /* IdentitiesService.swift */,
|
D0EC8DCA24DFA06700A08489 /* IdentitiesService.swift */,
|
||||||
D0EC8DC124DF7D9C00A08489 /* IdentityService.swift */,
|
D0EC8DC124DF7D9C00A08489 /* IdentityService.swift */,
|
||||||
D0EC8DC424DF842700A08489 /* KeychainService.swift */,
|
D0EC8DC424DF842700A08489 /* KeychainService.swift */,
|
||||||
D0EC8DD724E096C900A08489 /* UserNotificationService.swift */,
|
|
||||||
D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */,
|
D0EC8DC724DF8B3C00A08489 /* SecretsService.swift */,
|
||||||
|
D0EC8DD724E096C900A08489 /* UserNotificationService.swift */,
|
||||||
);
|
);
|
||||||
path = Services;
|
path = Services;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -500,6 +545,15 @@
|
||||||
path = macOS;
|
path = macOS;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D054951024EB101F008B00A5 /* Status List Services */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D054951124EB1041008B00A5 /* StatusListService.swift */,
|
||||||
|
D054951424EB1053008B00A5 /* TimelineService.swift */,
|
||||||
|
);
|
||||||
|
path = "Status List Services";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D0666A2224C677B400F3F04B /* Tests */ = {
|
D0666A2224C677B400F3F04B /* Tests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -519,14 +573,22 @@
|
||||||
D0666A6224C6DC6C00F3F04B /* AppAuthorization.swift */,
|
D0666A6224C6DC6C00F3F04B /* AppAuthorization.swift */,
|
||||||
D052BBCC24D750A100A80A7A /* AppEnvironment.swift */,
|
D052BBCC24D750A100A80A7A /* AppEnvironment.swift */,
|
||||||
D0ED1BD624CF94B200B4899C /* Application.swift */,
|
D0ED1BD624CF94B200B4899C /* Application.swift */,
|
||||||
|
D05494E924EA3F54008B00A5 /* Attachment.swift */,
|
||||||
|
D05494EF24EA3FE5008B00A5 /* Card.swift */,
|
||||||
D0666A5324C6C3E500F3F04B /* Emoji.swift */,
|
D0666A5324C6C3E500F3F04B /* Emoji.swift */,
|
||||||
D0666A4A24C6C37700F3F04B /* Identity.swift */,
|
D0666A4A24C6C37700F3F04B /* Identity.swift */,
|
||||||
D0666A4D24C6C39600F3F04B /* Instance.swift */,
|
D0666A4D24C6C39600F3F04B /* Instance.swift */,
|
||||||
|
D057426C24EA339300839EBA /* ListTimeline.swift */,
|
||||||
D0ED1BE224CFA84400B4899C /* MastodonError.swift */,
|
D0ED1BE224CFA84400B4899C /* MastodonError.swift */,
|
||||||
D0CD847224DBDEC700CF380C /* MastodonPreferences.swift */,
|
D0CD847224DBDEC700CF380C /* MastodonPreferences.swift */,
|
||||||
|
D05494E624EA3F1A008B00A5 /* Mention.swift */,
|
||||||
|
D05494EC24EA3FA9008B00A5 /* Poll.swift */,
|
||||||
D0E5362F24E5436C00FB1CE1 /* PushNotification.swift */,
|
D0E5362F24E5436C00FB1CE1 /* PushNotification.swift */,
|
||||||
D0EC8DEA24E26F1100A08489 /* PushSubscription.swift */,
|
D0EC8DEA24E26F1100A08489 /* PushSubscription.swift */,
|
||||||
D0CD847524DBDF3C00CF380C /* Status.swift */,
|
D0CD847524DBDF3C00CF380C /* Status.swift */,
|
||||||
|
D05494E324EA3EF7008B00A5 /* Tag.swift */,
|
||||||
|
D057426924EA32AC00839EBA /* Timeline.swift */,
|
||||||
|
D054951A24EB2825008B00A5 /* TransientStatusCollection.swift */,
|
||||||
D0CD847B24DBEA9F00CF380C /* Unknowable.swift */,
|
D0CD847B24DBEA9F00CF380C /* Unknowable.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
|
@ -555,7 +617,7 @@
|
||||||
D0091B6724DC10B30040E8D2 /* PostingReadingPreferencesView.swift */,
|
D0091B6724DC10B30040E8D2 /* PostingReadingPreferencesView.swift */,
|
||||||
D0091B6D24DD68090040E8D2 /* PreferencesView.swift */,
|
D0091B6D24DD68090040E8D2 /* PreferencesView.swift */,
|
||||||
D0BEC93A24C96FD500E864C4 /* RootView.swift */,
|
D0BEC93A24C96FD500E864C4 /* RootView.swift */,
|
||||||
D0BEC94924CA231200E864C4 /* TimelineView.swift */,
|
D0BEC94924CA231200E864C4 /* StatusesView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -579,7 +641,7 @@
|
||||||
D0091B6A24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift */,
|
D0091B6A24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift */,
|
||||||
D0091B7024DD68220040E8D2 /* PreferencesViewModel.swift */,
|
D0091B7024DD68220040E8D2 /* PreferencesViewModel.swift */,
|
||||||
D0BEC93724C9632800E864C4 /* RootViewModel.swift */,
|
D0BEC93724C9632800E864C4 /* RootViewModel.swift */,
|
||||||
D0BEC94624CA22C400E864C4 /* TimelineViewModel.swift */,
|
D0BEC94624CA22C400E864C4 /* StatusesViewModel.swift */,
|
||||||
);
|
);
|
||||||
path = "View Models";
|
path = "View Models";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -607,6 +669,7 @@
|
||||||
D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */,
|
D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */,
|
||||||
D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */,
|
D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */,
|
||||||
D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */,
|
D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */,
|
||||||
|
D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */,
|
||||||
);
|
);
|
||||||
path = "Mastodon API Stubs";
|
path = "Mastodon API Stubs";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -641,6 +704,7 @@
|
||||||
D0ED1BB224CE3A1600B4899C /* Development Assets */ = {
|
D0ED1BB224CE3A1600B4899C /* Development Assets */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */,
|
||||||
D04FD74124D4AA34007D572D /* DevelopmentModels.swift */,
|
D04FD74124D4AA34007D572D /* DevelopmentModels.swift */,
|
||||||
D0DC175724D0130800A75C65 /* HTTPStubs.swift */,
|
D0DC175724D0130800A75C65 /* HTTPStubs.swift */,
|
||||||
D0DC174824CFF13700A75C65 /* Mastodon API Stubs */,
|
D0DC174824CFF13700A75C65 /* Mastodon API Stubs */,
|
||||||
|
@ -817,6 +881,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D054950124EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */,
|
||||||
D047FAB224C3E21200AF17C5 /* Assets.xcassets in Resources */,
|
D047FAB224C3E21200AF17C5 /* Assets.xcassets in Resources */,
|
||||||
D06B491F24D3F7FE00642749 /* Localizable.strings in Resources */,
|
D06B491F24D3F7FE00642749 /* Localizable.strings in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -826,6 +891,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D054950224EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */,
|
||||||
D047FAB324C3E21200AF17C5 /* Assets.xcassets in Resources */,
|
D047FAB324C3E21200AF17C5 /* Assets.xcassets in Resources */,
|
||||||
D06B492024D3FB8000642749 /* Localizable.strings in Resources */,
|
D06B492024D3FB8000642749 /* Localizable.strings in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -890,21 +956,27 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */,
|
D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */,
|
||||||
|
D05494F724EA49F7008B00A5 /* TimelinesEndpoint.swift in Sources */,
|
||||||
|
D054951B24EB2825008B00A5 /* TransientStatusCollection.swift in Sources */,
|
||||||
D0DB6F0924C65AC000D965FE /* AddIdentityViewModel.swift in Sources */,
|
D0DB6F0924C65AC000D965FE /* AddIdentityViewModel.swift in Sources */,
|
||||||
D0CD847324DBDEC700CF380C /* MastodonPreferences.swift in Sources */,
|
D0CD847324DBDEC700CF380C /* MastodonPreferences.swift in Sources */,
|
||||||
D075817924E6657B0081F6A3 /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
D075817924E6657B0081F6A3 /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
||||||
D0ED1BD724CF94B200B4899C /* Application.swift in Sources */,
|
D0ED1BD724CF94B200B4899C /* Application.swift in Sources */,
|
||||||
D047FAAE24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
D047FAAE24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
||||||
D0BEC94724CA22C400E864C4 /* TimelineViewModel.swift in Sources */,
|
D0BEC94724CA22C400E864C4 /* StatusesViewModel.swift in Sources */,
|
||||||
D0666A4E24C6C39600F3F04B /* Instance.swift in Sources */,
|
D0666A4E24C6C39600F3F04B /* Instance.swift in Sources */,
|
||||||
|
D057426724E9FE1D00839EBA /* ContentDatabase.swift in Sources */,
|
||||||
|
D054951524EB1053008B00A5 /* TimelineService.swift in Sources */,
|
||||||
D019E6E924DF72E700697C7D /* InstanceEndpoint.swift in Sources */,
|
D019E6E924DF72E700697C7D /* InstanceEndpoint.swift in Sources */,
|
||||||
D0ED1BE324CFA84400B4899C /* MastodonError.swift in Sources */,
|
D0ED1BE324CFA84400B4899C /* MastodonError.swift in Sources */,
|
||||||
D0EC8DE824E21FEC00A08489 /* Data+Extensions.swift in Sources */,
|
D0EC8DE824E21FEC00A08489 /* Data+Extensions.swift in Sources */,
|
||||||
D0666A6324C6DC6C00F3F04B /* AppAuthorization.swift in Sources */,
|
D0666A6324C6DC6C00F3F04B /* AppAuthorization.swift in Sources */,
|
||||||
|
D05494F024EA3FE5008B00A5 /* Card.swift in Sources */,
|
||||||
D019E6E524DF72E700697C7D /* AccountEndpoint.swift in Sources */,
|
D019E6E524DF72E700697C7D /* AccountEndpoint.swift in Sources */,
|
||||||
D075817C24E6659A0081F6A3 /* NotificationTypesPreferencesView.swift in Sources */,
|
D075817C24E6659A0081F6A3 /* NotificationTypesPreferencesView.swift in Sources */,
|
||||||
D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */,
|
D065F53B24D3B33A00741304 /* View+Extensions.swift in Sources */,
|
||||||
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
|
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
|
||||||
|
D05494E424EA3EF7008B00A5 /* Tag.swift in Sources */,
|
||||||
D0159F8A24DE742F00E78478 /* IdentitiesViewModel.swift in Sources */,
|
D0159F8A24DE742F00E78478 /* IdentitiesViewModel.swift in Sources */,
|
||||||
D0666A5124C6C3BC00F3F04B /* Account.swift in Sources */,
|
D0666A5124C6C3BC00F3F04B /* Account.swift in Sources */,
|
||||||
D019E6E124DF72E700697C7D /* AppAuthorizationEndpoint.swift in Sources */,
|
D019E6E124DF72E700697C7D /* AppAuthorizationEndpoint.swift in Sources */,
|
||||||
|
@ -920,13 +992,15 @@
|
||||||
D019E6E324DF72E700697C7D /* PreferencesEndpoint.swift in Sources */,
|
D019E6E324DF72E700697C7D /* PreferencesEndpoint.swift in Sources */,
|
||||||
D0666A4B24C6C37700F3F04B /* Identity.swift in Sources */,
|
D0666A4B24C6C37700F3F04B /* Identity.swift in Sources */,
|
||||||
D0666A5424C6C3E500F3F04B /* Emoji.swift in Sources */,
|
D0666A5424C6C3E500F3F04B /* Emoji.swift in Sources */,
|
||||||
|
D05494FA24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */,
|
||||||
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */,
|
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */,
|
||||||
D0DC175524D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift in Sources */,
|
D0DC175524D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift in Sources */,
|
||||||
D0B23F0D24D210E90066F411 /* NSError+Extensions.swift in Sources */,
|
D0B23F0D24D210E90066F411 /* NSError+Extensions.swift in Sources */,
|
||||||
|
D05494ED24EA3FA9008B00A5 /* Poll.swift in Sources */,
|
||||||
D019E6D924DF728400697C7D /* MastodonDecoder.swift in Sources */,
|
D019E6D924DF728400697C7D /* MastodonDecoder.swift in Sources */,
|
||||||
D052BBCA24D74C9200A80A7A /* MockUserDefaults.swift in Sources */,
|
D052BBCA24D74C9200A80A7A /* MockUserDefaults.swift in Sources */,
|
||||||
D0DC175224D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
|
D0DC175224D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
|
||||||
D0BEC94A24CA231200E864C4 /* TimelineView.swift in Sources */,
|
D0BEC94A24CA231200E864C4 /* StatusesView.swift in Sources */,
|
||||||
D0BEC93B24C96FD500E864C4 /* RootView.swift in Sources */,
|
D0BEC93B24C96FD500E864C4 /* RootView.swift in Sources */,
|
||||||
D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */,
|
D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */,
|
||||||
D0EC8DC524DF842700A08489 /* KeychainService.swift in Sources */,
|
D0EC8DC524DF842700A08489 /* KeychainService.swift in Sources */,
|
||||||
|
@ -941,6 +1015,7 @@
|
||||||
D0EC8DE524E0B44500A08489 /* UserNotificationService.swift in Sources */,
|
D0EC8DE524E0B44500A08489 /* UserNotificationService.swift in Sources */,
|
||||||
D0EC8DCB24DFA06700A08489 /* IdentitiesService.swift in Sources */,
|
D0EC8DCB24DFA06700A08489 /* IdentitiesService.swift in Sources */,
|
||||||
D0091B7124DD68220040E8D2 /* PreferencesViewModel.swift in Sources */,
|
D0091B7124DD68220040E8D2 /* PreferencesViewModel.swift in Sources */,
|
||||||
|
D057426A24EA32AC00839EBA /* Timeline.swift in Sources */,
|
||||||
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */,
|
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */,
|
||||||
D0091B6B24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */,
|
D0091B6B24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */,
|
||||||
D0159F8624DE742F00E78478 /* TabNavigationViewModel.swift in Sources */,
|
D0159F8624DE742F00E78478 /* TabNavigationViewModel.swift in Sources */,
|
||||||
|
@ -952,13 +1027,16 @@
|
||||||
D0DB6EF424C5228A00D965FE /* AddIdentityView.swift in Sources */,
|
D0DB6EF424C5228A00D965FE /* AddIdentityView.swift in Sources */,
|
||||||
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */,
|
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */,
|
||||||
D0159FA524DE989700E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0159FA524DE989700E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
|
D057426D24EA339300839EBA /* ListTimeline.swift in Sources */,
|
||||||
D0ED1BCE24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
D0ED1BCE24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
||||||
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
||||||
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
||||||
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
|
D054951224EB1041008B00A5 /* StatusListService.swift in Sources */,
|
||||||
D0159F9124DE743700E78478 /* TabNavigationView.swift in Sources */,
|
D0159F9124DE743700E78478 /* TabNavigationView.swift in Sources */,
|
||||||
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
|
||||||
D0EC8DC824DF8B3C00A08489 /* SecretsService.swift in Sources */,
|
D0EC8DC824DF8B3C00A08489 /* SecretsService.swift in Sources */,
|
||||||
|
D05494E724EA3F1A008B00A5 /* Mention.swift in Sources */,
|
||||||
D0159FA324DE955900E78478 /* CustomEmojiText.swift in Sources */,
|
D0159FA324DE955900E78478 /* CustomEmojiText.swift in Sources */,
|
||||||
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
|
||||||
D0EC8DCE24DFB64200A08489 /* AuthenticationService.swift in Sources */,
|
D0EC8DCE24DFB64200A08489 /* AuthenticationService.swift in Sources */,
|
||||||
|
@ -969,6 +1047,7 @@
|
||||||
D0666A6F24C6DFB300F3F04B /* AccessToken.swift in Sources */,
|
D0666A6F24C6DFB300F3F04B /* AccessToken.swift in Sources */,
|
||||||
D0ED1BCB24CF744200B4899C /* MastodonClient.swift in Sources */,
|
D0ED1BCB24CF744200B4899C /* MastodonClient.swift in Sources */,
|
||||||
D0091B6824DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */,
|
D0091B6824DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */,
|
||||||
|
D05494EA24EA3F54008B00A5 /* Attachment.swift in Sources */,
|
||||||
D0CD847624DBDF3C00CF380C /* Status.swift in Sources */,
|
D0CD847624DBDF3C00CF380C /* Status.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -983,7 +1062,7 @@
|
||||||
D0CD847424DBDEC700CF380C /* MastodonPreferences.swift in Sources */,
|
D0CD847424DBDEC700CF380C /* MastodonPreferences.swift in Sources */,
|
||||||
D0ED1BD824CF94B200B4899C /* Application.swift in Sources */,
|
D0ED1BD824CF94B200B4899C /* Application.swift in Sources */,
|
||||||
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
D047FAAF24C3E21200AF17C5 /* MetatextApp.swift in Sources */,
|
||||||
D0BEC94824CA22C400E864C4 /* TimelineViewModel.swift in Sources */,
|
D0BEC94824CA22C400E864C4 /* StatusesViewModel.swift in Sources */,
|
||||||
D0159FA624DE98F600E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0159FA624DE98F600E78478 /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0EC8DC324DF7D9C00A08489 /* IdentityService.swift in Sources */,
|
D0EC8DC324DF7D9C00A08489 /* IdentityService.swift in Sources */,
|
||||||
D0666A4F24C6C39600F3F04B /* Instance.swift in Sources */,
|
D0666A4F24C6C39600F3F04B /* Instance.swift in Sources */,
|
||||||
|
@ -992,6 +1071,8 @@
|
||||||
D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */,
|
D065F53C24D3B33A00741304 /* View+Extensions.swift in Sources */,
|
||||||
D0DC174B24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
|
D0DC174B24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
|
||||||
D0666A5224C6C3BC00F3F04B /* Account.swift in Sources */,
|
D0666A5224C6C3BC00F3F04B /* Account.swift in Sources */,
|
||||||
|
D05494EE24EA3FA9008B00A5 /* Poll.swift in Sources */,
|
||||||
|
D05494FB24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */,
|
||||||
D052BBD124D750CA00A80A7A /* AppEnvironment.swift in Sources */,
|
D052BBD124D750CA00A80A7A /* AppEnvironment.swift in Sources */,
|
||||||
D081A40624D0F1A8001B016E /* String+Extensions.swift in Sources */,
|
D081A40624D0F1A8001B016E /* String+Extensions.swift in Sources */,
|
||||||
D0BEC93924C9632800E864C4 /* RootViewModel.swift in Sources */,
|
D0BEC93924C9632800E864C4 /* RootViewModel.swift in Sources */,
|
||||||
|
@ -1008,18 +1089,25 @@
|
||||||
D052BBCB24D74C9300A80A7A /* MockUserDefaults.swift in Sources */,
|
D052BBCB24D74C9300A80A7A /* MockUserDefaults.swift in Sources */,
|
||||||
D0DC175324D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
|
D0DC175324D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
|
||||||
D0EC8DE924E21FEC00A08489 /* Data+Extensions.swift in Sources */,
|
D0EC8DE924E21FEC00A08489 /* Data+Extensions.swift in Sources */,
|
||||||
D0BEC94B24CA231200E864C4 /* TimelineView.swift in Sources */,
|
D0BEC94B24CA231200E864C4 /* StatusesView.swift in Sources */,
|
||||||
|
D05494E524EA3EF7008B00A5 /* Tag.swift in Sources */,
|
||||||
D0BEC93C24C96FD500E864C4 /* RootView.swift in Sources */,
|
D0BEC93C24C96FD500E864C4 /* RootView.swift in Sources */,
|
||||||
D0159F9B24DE748900E78478 /* SidebarNavigationViewModel.swift in Sources */,
|
D0159F9B24DE748900E78478 /* SidebarNavigationViewModel.swift in Sources */,
|
||||||
D04FD74324D4AA34007D572D /* DevelopmentModels.swift in Sources */,
|
D04FD74324D4AA34007D572D /* DevelopmentModels.swift in Sources */,
|
||||||
D0DC175924D0130800A75C65 /* HTTPStubs.swift in Sources */,
|
D0DC175924D0130800A75C65 /* HTTPStubs.swift in Sources */,
|
||||||
|
D05494E824EA3F1A008B00A5 /* Mention.swift in Sources */,
|
||||||
|
D057426B24EA32AC00839EBA /* Timeline.swift in Sources */,
|
||||||
D019E6DA24DF728400697C7D /* MastodonDecoder.swift in Sources */,
|
D019E6DA24DF728400697C7D /* MastodonDecoder.swift in Sources */,
|
||||||
|
D05494EB24EA3F54008B00A5 /* Attachment.swift in Sources */,
|
||||||
D0DC177824D0CF2600A75C65 /* MockKeychainService.swift in Sources */,
|
D0DC177824D0CF2600A75C65 /* MockKeychainService.swift in Sources */,
|
||||||
D0C963FC24CC359D003BD330 /* AlertItem.swift in Sources */,
|
D0C963FC24CC359D003BD330 /* AlertItem.swift in Sources */,
|
||||||
D019E6E224DF72E700697C7D /* AppAuthorizationEndpoint.swift in Sources */,
|
D019E6E224DF72E700697C7D /* AppAuthorizationEndpoint.swift in Sources */,
|
||||||
D0DC174724CFEC2000A75C65 /* StubbingURLProtocol.swift in Sources */,
|
D0DC174724CFEC2000A75C65 /* StubbingURLProtocol.swift in Sources */,
|
||||||
|
D05494F824EA49F7008B00A5 /* TimelinesEndpoint.swift in Sources */,
|
||||||
D0091B7224DD68220040E8D2 /* PreferencesViewModel.swift in Sources */,
|
D0091B7224DD68220040E8D2 /* PreferencesViewModel.swift in Sources */,
|
||||||
|
D054951624EB1053008B00A5 /* TimelineService.swift in Sources */,
|
||||||
D019E6D824DF728400697C7D /* MastodonEncoder.swift in Sources */,
|
D019E6D824DF728400697C7D /* MastodonEncoder.swift in Sources */,
|
||||||
|
D054951C24EB2825008B00A5 /* TransientStatusCollection.swift in Sources */,
|
||||||
D0DC174E24CFF1F100A75C65 /* Stubbing.swift in Sources */,
|
D0DC174E24CFF1F100A75C65 /* Stubbing.swift in Sources */,
|
||||||
D0091B6C24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */,
|
D0091B6C24DC10CE0040E8D2 /* PostingReadingPreferencesViewModel.swift in Sources */,
|
||||||
D0091B6F24DD68090040E8D2 /* PreferencesView.swift in Sources */,
|
D0091B6F24DD68090040E8D2 /* PreferencesView.swift in Sources */,
|
||||||
|
@ -1028,9 +1116,11 @@
|
||||||
D0DB6EF524C5233E00D965FE /* AddIdentityView.swift in Sources */,
|
D0DB6EF524C5233E00D965FE /* AddIdentityView.swift in Sources */,
|
||||||
D019E6EA24DF72E700697C7D /* InstanceEndpoint.swift in Sources */,
|
D019E6EA24DF72E700697C7D /* InstanceEndpoint.swift in Sources */,
|
||||||
D0EC8DCF24DFB64200A08489 /* AuthenticationService.swift in Sources */,
|
D0EC8DCF24DFB64200A08489 /* AuthenticationService.swift in Sources */,
|
||||||
|
D054951324EB1041008B00A5 /* StatusListService.swift in Sources */,
|
||||||
D0159F9C24DE748C00E78478 /* SidebarNavigationView.swift in Sources */,
|
D0159F9C24DE748C00E78478 /* SidebarNavigationView.swift in Sources */,
|
||||||
D019E6F124DF7C2F00697C7D /* DatabaseError.swift in Sources */,
|
D019E6F124DF7C2F00697C7D /* DatabaseError.swift in Sources */,
|
||||||
D074577824D29006004758DB /* MockWebAuthSession.swift in Sources */,
|
D074577824D29006004758DB /* MockWebAuthSession.swift in Sources */,
|
||||||
|
D057426E24EA339300839EBA /* ListTimeline.swift in Sources */,
|
||||||
D0ED1BCF24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
D0ED1BCF24CF768200B4899C /* MastodonEndpoint.swift in Sources */,
|
||||||
D074577B24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
D074577B24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
|
||||||
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */,
|
||||||
|
@ -1050,6 +1140,8 @@
|
||||||
D0ED1BCC24CF744200B4899C /* MastodonClient.swift in Sources */,
|
D0ED1BCC24CF744200B4899C /* MastodonClient.swift in Sources */,
|
||||||
D0091B6924DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */,
|
D0091B6924DC10B30040E8D2 /* PostingReadingPreferencesView.swift in Sources */,
|
||||||
D075817A24E6657B0081F6A3 /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
D075817A24E6657B0081F6A3 /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
||||||
|
D05494F124EA3FE5008B00A5 /* Card.swift in Sources */,
|
||||||
|
D057426824E9FE1D00839EBA /* ContentDatabase.swift in Sources */,
|
||||||
D019E6E624DF72E700697C7D /* AccountEndpoint.swift in Sources */,
|
D019E6E624DF72E700697C7D /* AccountEndpoint.swift in Sources */,
|
||||||
D0CD847724DBDF3C00CF380C /* Status.swift in Sources */,
|
D0CD847724DBDF3C00CF380C /* Status.swift in Sources */,
|
||||||
D0EC8DEF24E2704D00A08489 /* PushSubscriptionEndpoint.swift in Sources */,
|
D0EC8DEF24E2704D00A08489 /* PushSubscriptionEndpoint.swift in Sources */,
|
||||||
|
|
410
Shared/Databases/ContentDatabase.swift
Normal file
410
Shared/Databases/ContentDatabase.swift
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import GRDB
|
||||||
|
|
||||||
|
// swiftlint:disable file_length
|
||||||
|
struct ContentDatabase {
|
||||||
|
private let databaseQueue: DatabaseQueue
|
||||||
|
|
||||||
|
init(identityID: UUID, inMemory: Bool = false) throws {
|
||||||
|
guard
|
||||||
|
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
||||||
|
.documentDirectory,
|
||||||
|
.userDomainMask, true)
|
||||||
|
.first
|
||||||
|
else { throw DatabaseError.documentsDirectoryNotFound }
|
||||||
|
|
||||||
|
if inMemory {
|
||||||
|
databaseQueue = DatabaseQueue()
|
||||||
|
} else {
|
||||||
|
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/\(identityID.uuidString).sqlite3")
|
||||||
|
}
|
||||||
|
|
||||||
|
try Self.migrate(databaseQueue)
|
||||||
|
try Self.createTemporaryTables(databaseQueue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ContentDatabase {
|
||||||
|
func insert(statuses: [Status], collection: StatusCollection? = nil) -> AnyPublisher<Void, Error> {
|
||||||
|
databaseQueue.writePublisher {
|
||||||
|
try collection?.save($0)
|
||||||
|
|
||||||
|
for status in statuses {
|
||||||
|
for component in status.storedComponents() {
|
||||||
|
try component.save($0)
|
||||||
|
}
|
||||||
|
|
||||||
|
try collection?.joinRecord(status: status).save($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func statusesObservation(timeline: Timeline) -> AnyPublisher<[Status], Error> {
|
||||||
|
ValueObservation
|
||||||
|
.tracking(timeline.statuses
|
||||||
|
.including(required: StoredStatus.account)
|
||||||
|
.including(optional: StoredStatus.reblogAccount)
|
||||||
|
.including(optional: StoredStatus.reblog)
|
||||||
|
.asRequest(of: StatusResult.self)
|
||||||
|
.fetchAll)
|
||||||
|
.removeDuplicates()
|
||||||
|
.publisher(in: databaseQueue)
|
||||||
|
.map { $0.map(Status.init(statusResult:)) }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ContentDatabase {
|
||||||
|
// swiftlint:disable function_body_length
|
||||||
|
static func migrate(_ writer: DatabaseWriter) throws {
|
||||||
|
var migrator = DatabaseMigrator()
|
||||||
|
|
||||||
|
migrator.registerMigration("createStatuses") { db in
|
||||||
|
try db.create(table: "account", ifNotExists: true) { t in
|
||||||
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
|
t.column("username", .text).notNull()
|
||||||
|
t.column("acct", .text).notNull()
|
||||||
|
t.column("displayName", .text).notNull()
|
||||||
|
t.column("locked", .boolean).notNull()
|
||||||
|
t.column("createdAt", .date).notNull()
|
||||||
|
t.column("followersCount", .integer).notNull()
|
||||||
|
t.column("followingCount", .integer).notNull()
|
||||||
|
t.column("statusesCount", .integer).notNull()
|
||||||
|
t.column("note", .text).notNull()
|
||||||
|
t.column("url", .text).notNull()
|
||||||
|
t.column("avatar", .text).notNull()
|
||||||
|
t.column("avatarStatic", .text).notNull()
|
||||||
|
t.column("header", .text).notNull()
|
||||||
|
t.column("headerStatic", .text).notNull()
|
||||||
|
t.column("fields", .blob).notNull()
|
||||||
|
t.column("emojis", .blob).notNull()
|
||||||
|
t.column("bot", .boolean).notNull()
|
||||||
|
t.column("moved", .boolean)
|
||||||
|
t.column("discoverable", .boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.create(table: "storedStatus", ifNotExists: true) { t in
|
||||||
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
|
t.column("uri", .text).notNull()
|
||||||
|
t.column("createdAt", .datetime).notNull()
|
||||||
|
t.column("accountId", .text).indexed().notNull().references("account", column: "id")
|
||||||
|
t.column("content", .text).notNull()
|
||||||
|
t.column("visibility", .text).notNull()
|
||||||
|
t.column("sensitive", .boolean).notNull()
|
||||||
|
t.column("spoilerText", .text).notNull()
|
||||||
|
t.column("mediaAttachments", .blob).notNull()
|
||||||
|
t.column("mentions", .blob).notNull()
|
||||||
|
t.column("tags", .blob).notNull()
|
||||||
|
t.column("emojis", .blob).notNull()
|
||||||
|
t.column("reblogsCount", .integer).notNull()
|
||||||
|
t.column("favouritesCount", .integer).notNull()
|
||||||
|
t.column("repliesCount", .integer).notNull()
|
||||||
|
t.column("application", .blob)
|
||||||
|
t.column("url", .text)
|
||||||
|
t.column("inReplyToId", .text)
|
||||||
|
t.column("inReplyToAccountId", .text)
|
||||||
|
t.column("reblogId", .text).indexed().references("storedStatus", column: "id")
|
||||||
|
t.column("poll", .blob)
|
||||||
|
t.column("card", .blob)
|
||||||
|
t.column("language", .text)
|
||||||
|
t.column("text", .text)
|
||||||
|
t.column("favourited", .boolean)
|
||||||
|
t.column("reblogged", .boolean)
|
||||||
|
t.column("muted", .boolean)
|
||||||
|
t.column("bookmarked", .boolean)
|
||||||
|
t.column("pinned", .boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.create(table: "timeline", ifNotExists: true) { t in
|
||||||
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
|
t.column("listTitle", .text)
|
||||||
|
}
|
||||||
|
|
||||||
|
try db.create(table: "timelineStatusJoin", ifNotExists: true) { t in
|
||||||
|
t.column("timelineId", .text)
|
||||||
|
.indexed()
|
||||||
|
.notNull()
|
||||||
|
.references("timeline", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
||||||
|
t.column("statusId", .text)
|
||||||
|
.indexed()
|
||||||
|
.notNull()
|
||||||
|
.references("storedStatus", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
||||||
|
|
||||||
|
t.primaryKey(["timelineId", "statusId"], onConflict: .replace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try migrator.migrate(writer)
|
||||||
|
}
|
||||||
|
// swiftlint:enable function_body_length
|
||||||
|
|
||||||
|
static func createTemporaryTables(_ writer: DatabaseWriter) throws {
|
||||||
|
try writer.write { database in
|
||||||
|
try database.create(table: "transientStatusCollection", temporary: true, ifNotExists: true) { t in
|
||||||
|
t.column("id", .text).notNull().primaryKey(onConflict: .replace)
|
||||||
|
}
|
||||||
|
|
||||||
|
try database.create(table: "transientStatusCollectionElement", temporary: true, ifNotExists: true) { t in
|
||||||
|
t.column("transientStatusCollectionId", .text)
|
||||||
|
.notNull()
|
||||||
|
.references("transientStatusCollection", column: "id", onDelete: .cascade, onUpdate: .cascade)
|
||||||
|
t.column("statusId", .text).notNull()
|
||||||
|
|
||||||
|
t.primaryKey(["transientStatusCollectionId", "statusId"], onConflict: .replace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Account: TableRecord, FetchableRecord, PersistableRecord {
|
||||||
|
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
|
||||||
|
MastodonDecoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
|
||||||
|
MastodonEncoder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol StatusCollection: FetchableRecord, PersistableRecord {
|
||||||
|
var id: String { get }
|
||||||
|
|
||||||
|
func joinRecord(status: Status) -> PersistableRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TimelineStatusJoin: Codable, TableRecord, FetchableRecord, PersistableRecord {
|
||||||
|
let timelineId: String
|
||||||
|
let statusId: String
|
||||||
|
|
||||||
|
static let status = belongsTo(StoredStatus.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Timeline: StatusCollection {
|
||||||
|
enum Columns: String, ColumnExpression {
|
||||||
|
case id, listTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
init(row: Row) {
|
||||||
|
switch row[Columns.id] as String {
|
||||||
|
case Timeline.home.id:
|
||||||
|
self = .home
|
||||||
|
case Timeline.local.id:
|
||||||
|
self = .local
|
||||||
|
case Timeline.federated.id:
|
||||||
|
self = .federated
|
||||||
|
default:
|
||||||
|
self = .list(MastodonList(id: row[Columns.id], title: row[Columns.listTitle]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to container: inout PersistenceContainer) {
|
||||||
|
container[Columns.id] = id
|
||||||
|
|
||||||
|
if case let .list(list) = self {
|
||||||
|
container[Columns.listTitle] = list.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinRecord(status: Status) -> PersistableRecord {
|
||||||
|
TimelineStatusJoin(timelineId: id, statusId: status.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Timeline {
|
||||||
|
static let statusJoins = hasMany(TimelineStatusJoin.self)
|
||||||
|
|
||||||
|
static let statuses = hasMany(StoredStatus.self,
|
||||||
|
through: statusJoins,
|
||||||
|
using: TimelineStatusJoin.status).order(Column("createdAt").desc)
|
||||||
|
|
||||||
|
var statusJoins: QueryInterfaceRequest<TimelineStatusJoin> {
|
||||||
|
request(for: Self.statusJoins)
|
||||||
|
}
|
||||||
|
|
||||||
|
var statuses: QueryInterfaceRequest<StoredStatus> {
|
||||||
|
request(for: Self.statuses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct TransientStatusCollectionElement: Codable, TableRecord, FetchableRecord, PersistableRecord {
|
||||||
|
let transientStatusCollectionId: String
|
||||||
|
let statusId: String
|
||||||
|
|
||||||
|
static let status = belongsTo(StoredStatus.self, key: "statusId")
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransientStatusCollection: StatusCollection {
|
||||||
|
func joinRecord(status: Status) -> PersistableRecord {
|
||||||
|
TransientStatusCollectionElement(transientStatusCollectionId: id, statusId: status.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension TransientStatusCollection {
|
||||||
|
static let elements = hasMany(TransientStatusCollectionElement.self)
|
||||||
|
|
||||||
|
var elements: QueryInterfaceRequest<TransientStatusCollectionElement> {
|
||||||
|
request(for: Self.elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct StoredStatus: Codable, Hashable {
|
||||||
|
let id: String
|
||||||
|
let uri: String
|
||||||
|
let createdAt: Date
|
||||||
|
let accountId: String
|
||||||
|
let content: String
|
||||||
|
let visibility: Status.Visibility
|
||||||
|
let sensitive: Bool
|
||||||
|
let spoilerText: String
|
||||||
|
let mediaAttachments: [Attachment]
|
||||||
|
let mentions: [Mention]
|
||||||
|
let tags: [Tag]
|
||||||
|
let emojis: [Emoji]
|
||||||
|
let reblogsCount: Int
|
||||||
|
let favouritesCount: Int
|
||||||
|
let repliesCount: Int
|
||||||
|
let application: Application?
|
||||||
|
let url: URL?
|
||||||
|
let inReplyToId: String?
|
||||||
|
let inReplyToAccountId: String?
|
||||||
|
let reblogId: String?
|
||||||
|
let poll: Poll?
|
||||||
|
let card: Card?
|
||||||
|
let language: String?
|
||||||
|
let text: String?
|
||||||
|
let favourited: Bool?
|
||||||
|
let reblogged: Bool?
|
||||||
|
let muted: Bool?
|
||||||
|
let bookmarked: Bool?
|
||||||
|
let pinned: Bool?
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension StoredStatus {
|
||||||
|
static let account = belongsTo(Account.self, key: "account")
|
||||||
|
static let reblogAccount = hasOne(Account.self, through: Self.reblog, using: Self.account, key: "reblogAccount")
|
||||||
|
static let reblog = belongsTo(StoredStatus.self, key: "reblog")
|
||||||
|
|
||||||
|
var account: QueryInterfaceRequest<Account> {
|
||||||
|
request(for: Self.account)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reblogAccount: QueryInterfaceRequest<Account> {
|
||||||
|
request(for: Self.reblogAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reblog: QueryInterfaceRequest<StoredStatus> {
|
||||||
|
request(for: Self.reblog)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(status: Status) {
|
||||||
|
id = status.id
|
||||||
|
uri = status.uri
|
||||||
|
createdAt = status.createdAt
|
||||||
|
accountId = status.account.id
|
||||||
|
content = status.content
|
||||||
|
visibility = status.visibility
|
||||||
|
sensitive = status.sensitive
|
||||||
|
spoilerText = status.spoilerText
|
||||||
|
mediaAttachments = status.mediaAttachments
|
||||||
|
mentions = status.mentions
|
||||||
|
tags = status.tags
|
||||||
|
emojis = status.emojis
|
||||||
|
reblogsCount = status.reblogsCount
|
||||||
|
favouritesCount = status.favouritesCount
|
||||||
|
repliesCount = status.repliesCount
|
||||||
|
application = status.application
|
||||||
|
url = status.url
|
||||||
|
inReplyToId = status.inReplyToId
|
||||||
|
inReplyToAccountId = status.inReplyToAccountId
|
||||||
|
reblogId = status.reblog?.id
|
||||||
|
poll = status.poll
|
||||||
|
card = status.card
|
||||||
|
language = status.language
|
||||||
|
text = status.text
|
||||||
|
favourited = status.favourited
|
||||||
|
reblogged = status.reblogged
|
||||||
|
muted = status.muted
|
||||||
|
bookmarked = status.bookmarked
|
||||||
|
pinned = status.pinned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord {
|
||||||
|
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
|
||||||
|
MastodonDecoder()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
|
||||||
|
MastodonEncoder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct StatusResult: Codable, Hashable, FetchableRecord {
|
||||||
|
let account: Account
|
||||||
|
let status: StoredStatus
|
||||||
|
let reblogAccount: Account?
|
||||||
|
let reblog: StoredStatus?
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension Status {
|
||||||
|
func storedComponents() -> [PersistableRecord] {
|
||||||
|
var components: [PersistableRecord] = [account]
|
||||||
|
|
||||||
|
if let reblog = reblog {
|
||||||
|
components.append(reblog.account)
|
||||||
|
components.append(StoredStatus(status: reblog))
|
||||||
|
}
|
||||||
|
|
||||||
|
components.append(StoredStatus(status: self))
|
||||||
|
|
||||||
|
return components
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(statusResult: StatusResult) {
|
||||||
|
var reblog: Status?
|
||||||
|
|
||||||
|
if let reblogResult = statusResult.reblog, let reblogAccount = statusResult.reblogAccount {
|
||||||
|
reblog = Status(storedStatus: reblogResult, account: reblogAccount, reblog: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.init(storedStatus: statusResult.status, account: statusResult.account, reblog: reblog)
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(storedStatus: StoredStatus, account: Account, reblog: Status?) {
|
||||||
|
self.init(
|
||||||
|
id: storedStatus.id,
|
||||||
|
uri: storedStatus.uri,
|
||||||
|
createdAt: storedStatus.createdAt,
|
||||||
|
account: account,
|
||||||
|
content: storedStatus.content,
|
||||||
|
visibility: storedStatus.visibility,
|
||||||
|
sensitive: storedStatus.sensitive,
|
||||||
|
spoilerText: storedStatus.spoilerText,
|
||||||
|
mediaAttachments: storedStatus.mediaAttachments,
|
||||||
|
mentions: storedStatus.mentions,
|
||||||
|
tags: storedStatus.tags,
|
||||||
|
emojis: storedStatus.emojis,
|
||||||
|
reblogsCount: storedStatus.reblogsCount,
|
||||||
|
favouritesCount: storedStatus.favouritesCount,
|
||||||
|
repliesCount: storedStatus.repliesCount,
|
||||||
|
application: storedStatus.application,
|
||||||
|
url: storedStatus.url,
|
||||||
|
inReplyToId: storedStatus.inReplyToId,
|
||||||
|
inReplyToAccountId: storedStatus.inReplyToAccountId,
|
||||||
|
reblog: reblog,
|
||||||
|
poll: storedStatus.poll,
|
||||||
|
card: storedStatus.card,
|
||||||
|
language: storedStatus.language,
|
||||||
|
text: storedStatus.text,
|
||||||
|
favourited: storedStatus.favourited,
|
||||||
|
reblogged: storedStatus.reblogged,
|
||||||
|
muted: storedStatus.muted,
|
||||||
|
bookmarked: storedStatus.bookmarked,
|
||||||
|
pinned: storedStatus.pinned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable file_length
|
|
@ -6,12 +6,15 @@ struct AppEnvironment {
|
||||||
let session: Session
|
let session: Session
|
||||||
let webAuthSessionType: WebAuthSession.Type
|
let webAuthSessionType: WebAuthSession.Type
|
||||||
let keychainServiceType: KeychainService.Type
|
let keychainServiceType: KeychainService.Type
|
||||||
let userDefaults: UserDefaults = .standard
|
let userDefaults: UserDefaults
|
||||||
|
let inMemoryContent: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppEnvironment {
|
extension AppEnvironment {
|
||||||
static let live: Self = Self(
|
static let live: Self = Self(
|
||||||
session: Session(configuration: .default),
|
session: Session(configuration: .default),
|
||||||
webAuthSessionType: LiveWebAuthSession.self,
|
webAuthSessionType: LiveWebAuthSession.self,
|
||||||
keychainServiceType: LiveKeychainService.self)
|
keychainServiceType: LiveKeychainService.self,
|
||||||
|
userDefaults: .standard,
|
||||||
|
inMemoryContent: false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Application: Codable {
|
struct Application: Codable, Hashable {
|
||||||
let name: String
|
let name: String
|
||||||
let website: String
|
let website: String?
|
||||||
}
|
}
|
||||||
|
|
43
Shared/Model/Attachment.swift
Normal file
43
Shared/Model/Attachment.swift
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Attachment: Codable, Hashable {
|
||||||
|
enum AttachmentType: String, Codable, Hashable, Unknowable {
|
||||||
|
case image, video, gifv, audio, unknown
|
||||||
|
|
||||||
|
static var unknownCase: Self { .unknown }
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:disable nesting
|
||||||
|
struct Meta: Codable, Hashable {
|
||||||
|
struct Info: Codable, Hashable {
|
||||||
|
let width: Int?
|
||||||
|
let height: Int?
|
||||||
|
let size: String?
|
||||||
|
let aspect: Double?
|
||||||
|
let frameRate: String?
|
||||||
|
let duration: Double?
|
||||||
|
let bitrate: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Focus: Codable, Hashable {
|
||||||
|
let x: Double
|
||||||
|
let y: Double
|
||||||
|
}
|
||||||
|
|
||||||
|
let original: Info?
|
||||||
|
let small: Info?
|
||||||
|
let focus: Focus?
|
||||||
|
}
|
||||||
|
// swiftlint:enable nesting
|
||||||
|
|
||||||
|
let id: String
|
||||||
|
let type: AttachmentType
|
||||||
|
let url: URL
|
||||||
|
let remoteUrl: URL?
|
||||||
|
let previewUrl: URL
|
||||||
|
let textUrl: URL?
|
||||||
|
let meta: Meta?
|
||||||
|
let description: String?
|
||||||
|
}
|
25
Shared/Model/Card.swift
Normal file
25
Shared/Model/Card.swift
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Card: Codable, Hashable {
|
||||||
|
enum CardType: String, Codable, Hashable, Unknowable {
|
||||||
|
case link, photo, video, rich, unknown
|
||||||
|
|
||||||
|
static var unknownCase: Self { .unknown }
|
||||||
|
}
|
||||||
|
|
||||||
|
let url: URL
|
||||||
|
let title: String
|
||||||
|
let description: String
|
||||||
|
let type: CardType
|
||||||
|
let authorName: String?
|
||||||
|
let authorUrl: String?
|
||||||
|
let providerName: String?
|
||||||
|
let providerUrl: String?
|
||||||
|
let html: String?
|
||||||
|
let width: Int?
|
||||||
|
let height: Int?
|
||||||
|
let image: URL?
|
||||||
|
let embedUrl: String?
|
||||||
|
}
|
8
Shared/Model/ListTimeline.swift
Normal file
8
Shared/Model/ListTimeline.swift
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct MastodonList: Codable, Hashable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let title: String
|
||||||
|
}
|
10
Shared/Model/Mention.swift
Normal file
10
Shared/Model/Mention.swift
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Mention: Codable, Hashable {
|
||||||
|
let url: URL
|
||||||
|
let username: String
|
||||||
|
let acct: String
|
||||||
|
let id: String
|
||||||
|
}
|
21
Shared/Model/Poll.swift
Normal file
21
Shared/Model/Poll.swift
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Poll: Codable, Hashable {
|
||||||
|
struct Option: Codable, Hashable {
|
||||||
|
var title: String
|
||||||
|
var votesCount: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
let id: String
|
||||||
|
let expiresAt: Date
|
||||||
|
let expired: Bool
|
||||||
|
let multiple: Bool
|
||||||
|
let votesCount: Int
|
||||||
|
let votersCount: Int?
|
||||||
|
let voted: Bool?
|
||||||
|
let ownVotes: [Int]?
|
||||||
|
let options: [Option]
|
||||||
|
let emojis: [Emoji]
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Status {
|
class Status: Codable, Identifiable {
|
||||||
enum Visibility: String, Codable, Unknowable {
|
enum Visibility: String, Codable, Unknowable {
|
||||||
case `public`
|
case `public`
|
||||||
case unlisted
|
case unlisted
|
||||||
|
@ -12,4 +12,96 @@ struct Status {
|
||||||
|
|
||||||
static var unknownCase: Self { .unknown }
|
static var unknownCase: Self { .unknown }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let id: String
|
||||||
|
let uri: String
|
||||||
|
let createdAt: Date
|
||||||
|
let account: Account
|
||||||
|
let content: String
|
||||||
|
let visibility: Visibility
|
||||||
|
let sensitive: Bool
|
||||||
|
let spoilerText: String
|
||||||
|
let mediaAttachments: [Attachment]
|
||||||
|
let mentions: [Mention]
|
||||||
|
let tags: [Tag]
|
||||||
|
let emojis: [Emoji]
|
||||||
|
let reblogsCount: Int
|
||||||
|
let favouritesCount: Int
|
||||||
|
let repliesCount: Int
|
||||||
|
let application: Application?
|
||||||
|
let url: URL?
|
||||||
|
let inReplyToId: String?
|
||||||
|
let inReplyToAccountId: String?
|
||||||
|
let reblog: Status?
|
||||||
|
let poll: Poll?
|
||||||
|
let card: Card?
|
||||||
|
let language: String?
|
||||||
|
let text: String?
|
||||||
|
let favourited: Bool?
|
||||||
|
let reblogged: Bool?
|
||||||
|
let muted: Bool?
|
||||||
|
let bookmarked: Bool?
|
||||||
|
let pinned: Bool?
|
||||||
|
|
||||||
|
// Xcode-generated memberwise initializer
|
||||||
|
init(
|
||||||
|
id: String,
|
||||||
|
uri: String,
|
||||||
|
createdAt: Date,
|
||||||
|
account: Account,
|
||||||
|
content: String,
|
||||||
|
visibility: Status.Visibility,
|
||||||
|
sensitive: Bool,
|
||||||
|
spoilerText: String,
|
||||||
|
mediaAttachments: [Attachment],
|
||||||
|
mentions: [Mention],
|
||||||
|
tags: [Tag],
|
||||||
|
emojis: [Emoji],
|
||||||
|
reblogsCount: Int,
|
||||||
|
favouritesCount: Int,
|
||||||
|
repliesCount: Int,
|
||||||
|
application: Application?,
|
||||||
|
url: URL?,
|
||||||
|
inReplyToId: String?,
|
||||||
|
inReplyToAccountId: String?,
|
||||||
|
reblog: Status?,
|
||||||
|
poll: Poll?,
|
||||||
|
card: Card?,
|
||||||
|
language: String?,
|
||||||
|
text: String?,
|
||||||
|
favourited: Bool?,
|
||||||
|
reblogged: Bool?,
|
||||||
|
muted: Bool?,
|
||||||
|
bookmarked: Bool?,
|
||||||
|
pinned: Bool?) {
|
||||||
|
self.id = id
|
||||||
|
self.uri = uri
|
||||||
|
self.createdAt = createdAt
|
||||||
|
self.account = account
|
||||||
|
self.content = content
|
||||||
|
self.visibility = visibility
|
||||||
|
self.sensitive = sensitive
|
||||||
|
self.spoilerText = spoilerText
|
||||||
|
self.mediaAttachments = mediaAttachments
|
||||||
|
self.mentions = mentions
|
||||||
|
self.tags = tags
|
||||||
|
self.emojis = emojis
|
||||||
|
self.reblogsCount = reblogsCount
|
||||||
|
self.favouritesCount = favouritesCount
|
||||||
|
self.repliesCount = repliesCount
|
||||||
|
self.application = application
|
||||||
|
self.url = url
|
||||||
|
self.inReplyToId = inReplyToId
|
||||||
|
self.inReplyToAccountId = inReplyToAccountId
|
||||||
|
self.reblog = reblog
|
||||||
|
self.poll = poll
|
||||||
|
self.card = card
|
||||||
|
self.language = language
|
||||||
|
self.text = text
|
||||||
|
self.favourited = favourited
|
||||||
|
self.reblogged = reblogged
|
||||||
|
self.muted = muted
|
||||||
|
self.bookmarked = bookmarked
|
||||||
|
self.pinned = pinned
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
8
Shared/Model/Tag.swift
Normal file
8
Shared/Model/Tag.swift
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Tag: Codable, Hashable {
|
||||||
|
let name: String
|
||||||
|
let url: URL
|
||||||
|
}
|
38
Shared/Model/Timeline.swift
Normal file
38
Shared/Model/Timeline.swift
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum Timeline {
|
||||||
|
case home
|
||||||
|
case local
|
||||||
|
case federated
|
||||||
|
case list(MastodonList)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Timeline {
|
||||||
|
var id: String {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return "home"
|
||||||
|
case .local:
|
||||||
|
return "local"
|
||||||
|
case .federated:
|
||||||
|
return "federated"
|
||||||
|
case let .list(list):
|
||||||
|
return list.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endpoint: TimelinesEndpoint {
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
return .home
|
||||||
|
case .local:
|
||||||
|
return .public(local: true)
|
||||||
|
case .federated:
|
||||||
|
return .public(local: false)
|
||||||
|
case let .list(list):
|
||||||
|
return .list(id: list.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class TimelineViewModel: ObservableObject {
|
struct TransientStatusCollection: Codable {
|
||||||
|
let id: String
|
||||||
}
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum TimelinesEndpoint {
|
||||||
|
case `public`(local: Bool)
|
||||||
|
case tag(String)
|
||||||
|
case home
|
||||||
|
case list(id: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TimelinesEndpoint: MastodonEndpoint {
|
||||||
|
typealias ResultType = [Status]
|
||||||
|
|
||||||
|
var context: [String] {
|
||||||
|
defaultContext + ["timelines"]
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathComponentsInContext: [String] {
|
||||||
|
switch self {
|
||||||
|
case .public:
|
||||||
|
return ["public"]
|
||||||
|
case let .tag(tag):
|
||||||
|
return ["tag", tag]
|
||||||
|
case .home:
|
||||||
|
return ["home"]
|
||||||
|
case let .list(id):
|
||||||
|
return ["list", id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters: [String: Any]? {
|
||||||
|
switch self {
|
||||||
|
case let .public(local):
|
||||||
|
return ["local": local]
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method: HTTPMethod { .get }
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ class IdentityService {
|
||||||
let observationErrors: AnyPublisher<Error, Never>
|
let observationErrors: AnyPublisher<Error, Never>
|
||||||
|
|
||||||
private let identityDatabase: IdentityDatabase
|
private let identityDatabase: IdentityDatabase
|
||||||
|
private let contentDatabase: ContentDatabase
|
||||||
private let environment: AppEnvironment
|
private let environment: AppEnvironment
|
||||||
private let networkClient: MastodonClient
|
private let networkClient: MastodonClient
|
||||||
private let secretsService: SecretsService
|
private let secretsService: SecretsService
|
||||||
|
@ -37,6 +38,8 @@ class IdentityService {
|
||||||
networkClient.instanceURL = identity.url
|
networkClient.instanceURL = identity.url
|
||||||
networkClient.accessToken = try? secretsService.item(.accessToken)
|
networkClient.accessToken = try? secretsService.item(.accessToken)
|
||||||
|
|
||||||
|
contentDatabase = try ContentDatabase(identityID: identityID, inMemory: environment.inMemoryContent)
|
||||||
|
|
||||||
observation.catch { [weak self] error -> Empty<Identity, Never> in
|
observation.catch { [weak self] error -> Empty<Identity, Never> in
|
||||||
self?.observationErrorsInput.send(error)
|
self?.observationErrorsInput.send(error)
|
||||||
|
|
||||||
|
@ -127,6 +130,10 @@ extension IdentityService {
|
||||||
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:))
|
.flatMap(identityDatabase.updatePushSubscription(alerts:deviceToken:forIdentityID:))
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func service(timeline: Timeline) -> StatusListService {
|
||||||
|
TimelineService(timeline: timeline, networkClient: networkClient, contentDatabase: contentDatabase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension IdentityService {
|
private extension IdentityService {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
protocol StatusListService {
|
||||||
|
var statusSections: AnyPublisher<[[Status]], Error> { get }
|
||||||
|
func request(maxID: String?, minID: String?) -> AnyPublisher<Void, Error>
|
||||||
|
}
|
28
Shared/Services/Status List Services/TimelineService.swift
Normal file
28
Shared/Services/Status List Services/TimelineService.swift
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
struct TimelineService: StatusListService {
|
||||||
|
let statusSections: AnyPublisher<[[Status]], Error>
|
||||||
|
|
||||||
|
private let timeline: Timeline
|
||||||
|
private let networkClient: MastodonClient
|
||||||
|
private let contentDatabase: ContentDatabase
|
||||||
|
|
||||||
|
init(timeline: Timeline, networkClient: MastodonClient, contentDatabase: ContentDatabase) {
|
||||||
|
self.timeline = timeline
|
||||||
|
self.networkClient = networkClient
|
||||||
|
self.contentDatabase = contentDatabase
|
||||||
|
statusSections = contentDatabase.statusesObservation(timeline: timeline)
|
||||||
|
.map { [$0] }
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
func request(maxID: String?, minID: String?) -> AnyPublisher<Void, Error> {
|
||||||
|
return networkClient.request(timeline.endpoint)
|
||||||
|
.map { ($0, timeline) }
|
||||||
|
.flatMap(contentDatabase.insert(statuses:collection:))
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
28
Shared/View Models/StatusesViewModel.swift
Normal file
28
Shared/View Models/StatusesViewModel.swift
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class StatusesViewModel: ObservableObject {
|
||||||
|
@Published var statusSections = [[Status]]()
|
||||||
|
@Published var alertItem: AlertItem?
|
||||||
|
private let statusListService: StatusListService
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
init(statusListService: StatusListService) {
|
||||||
|
self.statusListService = statusListService
|
||||||
|
|
||||||
|
statusListService.statusSections
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.assign(to: &$statusSections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusesViewModel {
|
||||||
|
func request(maxID: String? = nil, minID: String? = nil) {
|
||||||
|
statusListService.request(maxID: maxID, minID: minID)
|
||||||
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
.sink {}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
}
|
31
Shared/Views/StatusesView.swift
Normal file
31
Shared/Views/StatusesView.swift
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct StatusesView: View {
|
||||||
|
@StateObject var viewModel: StatusesViewModel
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
LazyVStack {
|
||||||
|
ForEach(Array(zip(viewModel.statusSections.indices, viewModel.statusSections)),
|
||||||
|
id: \.0) { _, statuses in
|
||||||
|
ForEach(statuses) { status in
|
||||||
|
Text(status.content)
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear { viewModel.request() }
|
||||||
|
.alertItem($viewModel.alertItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
struct StatusesView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
StatusesView(viewModel: .development)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -1,17 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct TimelineView: View {
|
|
||||||
var body: some View {
|
|
||||||
Text("Time of my life")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
struct TimelineView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
TimelineView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -6,6 +6,7 @@ import Combine
|
||||||
class TabNavigationViewModel: ObservableObject {
|
class TabNavigationViewModel: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published private(set) var identity: Identity
|
||||||
@Published private(set) var recentIdentities = [Identity]()
|
@Published private(set) var recentIdentities = [Identity]()
|
||||||
|
@Published private(set) var timelineViewModel: StatusesViewModel
|
||||||
@Published var presentingSecondaryNavigation = false
|
@Published var presentingSecondaryNavigation = false
|
||||||
@Published var alertItem: AlertItem?
|
@Published var alertItem: AlertItem?
|
||||||
var selectedTab: Tab? = .timelines
|
var selectedTab: Tab? = .timelines
|
||||||
|
@ -16,6 +17,7 @@ class TabNavigationViewModel: ObservableObject {
|
||||||
init(identityService: IdentityService) {
|
init(identityService: IdentityService) {
|
||||||
self.identityService = identityService
|
self.identityService = identityService
|
||||||
identity = identityService.identity
|
identity = identityService.identity
|
||||||
|
timelineViewModel = StatusesViewModel(statusListService: identityService.service(timeline: .home))
|
||||||
identityService.$identity.dropFirst().assign(to: &$identity)
|
identityService.$identity.dropFirst().assign(to: &$identity)
|
||||||
|
|
||||||
identityService.recentIdentitiesObservation()
|
identityService.recentIdentitiesObservation()
|
||||||
|
|
|
@ -40,7 +40,7 @@ private extension TabNavigationView {
|
||||||
func view(tab: TabNavigationViewModel.Tab) -> some View {
|
func view(tab: TabNavigationViewModel.Tab) -> some View {
|
||||||
switch tab {
|
switch tab {
|
||||||
case .timelines:
|
case .timelines:
|
||||||
TimelineView()
|
StatusesView(viewModel: viewModel.timelineViewModel)
|
||||||
.navigationBarTitle(viewModel.identity.handle, displayMode: .inline)
|
.navigationBarTitle(viewModel.identity.handle, displayMode: .inline)
|
||||||
.navigationBarItems(
|
.navigationBarItems(
|
||||||
leading: Button {
|
leading: Button {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Combine
|
||||||
|
|
||||||
class SidebarNavigationViewModel: ObservableObject {
|
class SidebarNavigationViewModel: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published private(set) var identity: Identity
|
||||||
|
@Published private(set) var timelineViewModel: StatusesViewModel
|
||||||
@Published var alertItem: AlertItem?
|
@Published var alertItem: AlertItem?
|
||||||
var selectedTab: Tab? = .timelines
|
var selectedTab: Tab? = .timelines
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ class SidebarNavigationViewModel: ObservableObject {
|
||||||
init(identityService: IdentityService) {
|
init(identityService: IdentityService) {
|
||||||
self.identityService = identityService
|
self.identityService = identityService
|
||||||
identity = identityService.identity
|
identity = identityService.identity
|
||||||
|
timelineViewModel = StatusesViewModel(statusListService: identityService.service(timeline: .home))
|
||||||
identityService.$identity.dropFirst().assign(to: &$identity)
|
identityService.$identity.dropFirst().assign(to: &$identity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ private extension SidebarNavigationView {
|
||||||
Group {
|
Group {
|
||||||
switch topLevelNavigation {
|
switch topLevelNavigation {
|
||||||
case .timelines:
|
case .timelines:
|
||||||
TimelineView()
|
StatusesView(viewModel: viewModel.timelineViewModel)
|
||||||
default: Text(topLevelNavigation.title)
|
default: Text(topLevelNavigation.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue