mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Modularize view models
This commit is contained in:
parent
bd1f7af036
commit
038385c2c7
60 changed files with 561 additions and 491 deletions
|
@ -3,22 +3,6 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
private static let HTTPSPrefix = "https://"
|
|
||||||
|
|
||||||
func url() throws -> URL {
|
|
||||||
let url: URL?
|
|
||||||
|
|
||||||
if hasPrefix(Self.HTTPSPrefix) {
|
|
||||||
url = URL(string: self)
|
|
||||||
} else {
|
|
||||||
url = URL(string: Self.HTTPSPrefix + self)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let validURL = url else { throw URLError(.badURL) }
|
|
||||||
|
|
||||||
return validURL
|
|
||||||
}
|
|
||||||
|
|
||||||
func countEmphasizedAttributedString(count: Int, highlighted: Bool = false) -> NSAttributedString {
|
func countEmphasizedAttributedString(count: Int, highlighted: Bool = false) -> NSAttributedString {
|
||||||
let countRange = (self as NSString).range(of: String.localizedStringWithFormat("%ld", count))
|
let countRange = (self as NSString).range(of: String.localizedStringWithFormat("%ld", count))
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func alertItem(_ alertItem: Binding<AlertItem?>) -> some View {
|
func alertItem(_ alertItem: Binding<AlertItem?>) -> some View {
|
||||||
|
|
|
@ -52,6 +52,6 @@ private extension Client {
|
||||||
|
|
||||||
return session.request(target)
|
return session.request(target)
|
||||||
.validate()
|
.validate()
|
||||||
.publishDecodable(type: T.ResultType.self, decoder: decoder)
|
.publishDecodable(type: T.ResultType.self, queue: session.rootQueue, decoder: decoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ public class StubbingURLProtocol: URLProtocol {
|
||||||
guard
|
guard
|
||||||
let url = request.url,
|
let url = request.url,
|
||||||
let stub = Self.stub(request: request, target: Self.targetsForURLs[url]) else {
|
let stub = Self.stub(request: request, target: Self.targetsForURLs[url]) else {
|
||||||
preconditionFailure("Stub for request not found")
|
// preconditionFailure("Stub for request not found")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch stub {
|
switch stub {
|
||||||
|
|
11
Mastodon/Sources/MastodonStubs/Paged+Stubbing.swift
Normal file
11
Mastodon/Sources/MastodonStubs/Paged+Stubbing.swift
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Mastodon
|
||||||
|
import Stubbing
|
||||||
|
|
||||||
|
extension Paged: Stubbing where T: Stubbing {
|
||||||
|
public func data(url: URL) -> Data? {
|
||||||
|
endpoint.data(url: url)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,25 +7,18 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0175CAB24FE2D6300B085F6 /* PreviewViewModels */; };
|
||||||
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; };
|
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; };
|
||||||
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; };
|
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; };
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
||||||
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */; };
|
|
||||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||||
D04FD74224D4AA34007D572D /* PreviewMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD74124D4AA34007D572D /* PreviewMocks.swift */; };
|
|
||||||
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC624D749C800A80A7A /* RootViewModelTests.swift */; };
|
|
||||||
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; };
|
|
||||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
||||||
D0BDF66724FD7CDA00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */; };
|
|
||||||
D0BDF66B24FD7CEC00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */; };
|
D0BDF66B24FD7CEC00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */; };
|
||||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
||||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
|
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
|
||||||
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */; };
|
|
||||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
|
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
|
||||||
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */; };
|
|
||||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */; };
|
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */; };
|
||||||
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */; };
|
|
||||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
||||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */; };
|
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */; };
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42424F76169001EBDBB /* AddIdentityView.swift */; };
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42424F76169001EBDBB /* AddIdentityView.swift */; };
|
||||||
|
@ -37,34 +30,20 @@
|
||||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
|
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
|
||||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
|
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
|
||||||
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; };
|
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; };
|
||||||
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45024F76169001EBDBB /* AlertItem.swift */; };
|
|
||||||
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
|
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
|
||||||
D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
||||||
D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45824F76169001EBDBB /* Localizable.stringsdict */; };
|
D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45824F76169001EBDBB /* Localizable.stringsdict */; };
|
||||||
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45A24F76169001EBDBB /* PostingReadingPreferencesViewModel.swift */; };
|
|
||||||
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45B24F76169001EBDBB /* SecondaryNavigationViewModel.swift */; };
|
|
||||||
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45C24F76169001EBDBB /* TabNavigationViewModel.swift */; };
|
|
||||||
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45D24F76169001EBDBB /* NotificationTypesPreferencesViewModel.swift */; };
|
|
||||||
D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45E24F76169001EBDBB /* RootViewModel.swift */; };
|
|
||||||
D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45F24F76169001EBDBB /* IdentitiesViewModel.swift */; };
|
|
||||||
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46024F76169001EBDBB /* AddIdentityViewModel.swift */; };
|
|
||||||
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */; };
|
|
||||||
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46224F76169001EBDBB /* StatusViewModel.swift */; };
|
|
||||||
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */; };
|
|
||||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46A24F76169001EBDBB /* String+Extensions.swift */; };
|
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46A24F76169001EBDBB /* String+Extensions.swift */; };
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */; };
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */; };
|
||||||
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
|
||||||
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */; };
|
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */; };
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
|
||||||
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47024F76169001EBDBB /* Date+Extensions.swift */; };
|
|
||||||
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47124F76169001EBDBB /* Data+Extensions.swift */; };
|
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47124F76169001EBDBB /* Data+Extensions.swift */; };
|
||||||
D0E2C1CE24FD7EE900854680 /* ServiceLayerMocks in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1CD24FD7EE900854680 /* ServiceLayerMocks */; };
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0E2C1D024FD97F000854680 /* ViewModels */; };
|
||||||
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
|
||||||
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -102,22 +81,16 @@
|
||||||
D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; };
|
D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StatusTableViewCell.xib; sourceTree = "<group>"; };
|
||||||
D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
|
||||||
D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
|
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
|
||||||
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D04FD74124D4AA34007D572D /* PreviewMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewMocks.swift; sourceTree = "<group>"; };
|
|
||||||
D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.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>"; };
|
||||||
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ServiceLayer; sourceTree = "<group>"; };
|
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ServiceLayer; sourceTree = "<group>"; };
|
||||||
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
|
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
|
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
|
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
|
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
|
||||||
D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0BFDAF524FC7C5300C86618 /* HTTP */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HTTP; sourceTree = "<group>"; };
|
D0BFDAF524FC7C5300C86618 /* HTTP */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HTTP; sourceTree = "<group>"; };
|
||||||
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
|
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
|
||||||
D0C7D41F24F76169001EBDBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0C7D41F24F76169001EBDBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -132,36 +105,23 @@
|
||||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; };
|
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = "<group>"; };
|
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = "<group>"; };
|
||||||
D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = "<group>"; };
|
D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = "<group>"; };
|
||||||
D0C7D45024F76169001EBDBB /* AlertItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
|
||||||
D0C7D45524F76169001EBDBB /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
D0C7D45524F76169001EBDBB /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
D0C7D45724F76169001EBDBB /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
D0C7D45724F76169001EBDBB /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||||
D0C7D45824F76169001EBDBB /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
|
D0C7D45824F76169001EBDBB /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = "<group>"; };
|
||||||
D0C7D45A24F76169001EBDBB /* PostingReadingPreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostingReadingPreferencesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45B24F76169001EBDBB /* SecondaryNavigationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryNavigationViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45C24F76169001EBDBB /* TabNavigationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45D24F76169001EBDBB /* NotificationTypesPreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45E24F76169001EBDBB /* RootViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D45F24F76169001EBDBB /* IdentitiesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentitiesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D46024F76169001EBDBB /* AddIdentityViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D46224F76169001EBDBB /* StatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewModel.swift; sourceTree = "<group>"; };
|
|
||||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Publisher+Extensions.swift"; sourceTree = "<group>"; };
|
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
|
|
||||||
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
|
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
|
||||||
|
D0E2C1CF24FD8BA400854680 /* ViewModels */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ViewModels; sourceTree = "<group>"; };
|
||||||
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||||
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
|
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
|
||||||
D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModelTests.swift; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -169,9 +129,9 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D0BDF66724FD7CDA00C7FA1C /* ServiceLayer in Frameworks */,
|
|
||||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
|
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
|
||||||
D0E2C1CE24FD7EE900854680 /* ServiceLayerMocks in Frameworks */,
|
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */,
|
||||||
|
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -179,7 +139,6 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -222,16 +181,14 @@
|
||||||
D0BFDAF524FC7C5300C86618 /* HTTP */,
|
D0BFDAF524FC7C5300C86618 /* HTTP */,
|
||||||
D0C7D45624F76169001EBDBB /* Localizations */,
|
D0C7D45624F76169001EBDBB /* Localizations */,
|
||||||
D0E0F1E424FC49FC002C04BF /* Mastodon */,
|
D0E0F1E424FC49FC002C04BF /* Mastodon */,
|
||||||
D0C7D43824F76169001EBDBB /* Model */,
|
|
||||||
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
|
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
|
||||||
D0ED1BB224CE3A1600B4899C /* Preview */,
|
|
||||||
D047FA8D24C3E21200AF17C5 /* Products */,
|
D047FA8D24C3E21200AF17C5 /* Products */,
|
||||||
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */,
|
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */,
|
||||||
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
||||||
D0C7D45324F76169001EBDBB /* System */,
|
D0C7D45324F76169001EBDBB /* System */,
|
||||||
D0666A2224C677B400F3F04B /* Tests */,
|
D0666A2224C677B400F3F04B /* Tests */,
|
||||||
D0C7D43024F76169001EBDBB /* View Controllers */,
|
D0C7D43024F76169001EBDBB /* View Controllers */,
|
||||||
D0C7D45924F76169001EBDBB /* View Models */,
|
D0E2C1CF24FD8BA400854680 /* ViewModels */,
|
||||||
D0C7D42024F76169001EBDBB /* Views */,
|
D0C7D42024F76169001EBDBB /* Views */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -250,7 +207,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0666A2524C677B400F3F04B /* Info.plist */,
|
D0666A2524C677B400F3F04B /* Info.plist */,
|
||||||
D0ED1B6C24CE0EED00B4899C /* View Models */,
|
|
||||||
);
|
);
|
||||||
path = Tests;
|
path = Tests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -302,14 +258,6 @@
|
||||||
path = "View Controllers";
|
path = "View Controllers";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D0C7D43824F76169001EBDBB /* Model */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D0C7D45024F76169001EBDBB /* AlertItem.swift */,
|
|
||||||
);
|
|
||||||
path = Model;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D0C7D45324F76169001EBDBB /* System */ = {
|
D0C7D45324F76169001EBDBB /* System */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -328,35 +276,12 @@
|
||||||
path = Localizations;
|
path = Localizations;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D0C7D45924F76169001EBDBB /* View Models */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D0C7D46024F76169001EBDBB /* AddIdentityViewModel.swift */,
|
|
||||||
D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */,
|
|
||||||
D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */,
|
|
||||||
D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */,
|
|
||||||
D0C7D45F24F76169001EBDBB /* IdentitiesViewModel.swift */,
|
|
||||||
D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */,
|
|
||||||
D0C7D45D24F76169001EBDBB /* NotificationTypesPreferencesViewModel.swift */,
|
|
||||||
D0C7D45A24F76169001EBDBB /* PostingReadingPreferencesViewModel.swift */,
|
|
||||||
D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */,
|
|
||||||
D0C7D45E24F76169001EBDBB /* RootViewModel.swift */,
|
|
||||||
D0C7D45B24F76169001EBDBB /* SecondaryNavigationViewModel.swift */,
|
|
||||||
D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */,
|
|
||||||
D0C7D46224F76169001EBDBB /* StatusViewModel.swift */,
|
|
||||||
D0C7D45C24F76169001EBDBB /* TabNavigationViewModel.swift */,
|
|
||||||
);
|
|
||||||
path = "View Models";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D0C7D46824F76169001EBDBB /* Extensions */ = {
|
D0C7D46824F76169001EBDBB /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
|
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
|
||||||
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */,
|
|
||||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||||
D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */,
|
|
||||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||||
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
||||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
|
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
|
||||||
|
@ -374,23 +299,6 @@
|
||||||
path = "Notification Service Extension";
|
path = "Notification Service Extension";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
D0ED1B6C24CE0EED00B4899C /* View Models */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */,
|
|
||||||
D052BBC624D749C800A80A7A /* RootViewModelTests.swift */,
|
|
||||||
);
|
|
||||||
path = "View Models";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D0ED1BB224CE3A1600B4899C /* Preview */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
D04FD74124D4AA34007D572D /* PreviewMocks.swift */,
|
|
||||||
);
|
|
||||||
path = Preview;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
@ -412,8 +320,8 @@
|
||||||
name = Metatext;
|
name = Metatext;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D06B492224D4611300642749 /* KingfisherSwiftUI */,
|
D06B492224D4611300642749 /* KingfisherSwiftUI */,
|
||||||
D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */,
|
D0E2C1D024FD97F000854680 /* ViewModels */,
|
||||||
D0E2C1CD24FD7EE900854680 /* ServiceLayerMocks */,
|
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */,
|
||||||
);
|
);
|
||||||
productName = "Metatext (iOS)";
|
productName = "Metatext (iOS)";
|
||||||
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
|
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
|
||||||
|
@ -434,7 +342,6 @@
|
||||||
);
|
);
|
||||||
name = Tests;
|
name = Tests;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
D065F53824D37E5100741304 /* CombineExpectations */,
|
|
||||||
);
|
);
|
||||||
productName = "Unit Tests";
|
productName = "Unit Tests";
|
||||||
productReference = D0666A2124C677B400F3F04B /* Tests.xctest */;
|
productReference = D0666A2124C677B400F3F04B /* Tests.xctest */;
|
||||||
|
@ -493,7 +400,6 @@
|
||||||
);
|
);
|
||||||
mainGroup = D047FA7F24C3E21000AF17C5;
|
mainGroup = D047FA7F24C3E21000AF17C5;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
|
||||||
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||||
);
|
);
|
||||||
productRefGroup = D047FA8D24C3E21200AF17C5 /* Products */;
|
productRefGroup = D047FA8D24C3E21200AF17C5 /* Products */;
|
||||||
|
@ -560,47 +466,29 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
|
||||||
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */,
|
|
||||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
||||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||||
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */,
|
|
||||||
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */,
|
|
||||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
||||||
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
|
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
|
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
|
||||||
D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */,
|
|
||||||
D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */,
|
|
||||||
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */,
|
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||||
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */,
|
|
||||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||||
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */,
|
|
||||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||||
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */,
|
|
||||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
||||||
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */,
|
|
||||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
||||||
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */,
|
|
||||||
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */,
|
|
||||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
||||||
D04FD74224D4AA34007D572D /* PreviewMocks.swift in Sources */,
|
|
||||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||||
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */,
|
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */,
|
||||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
||||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
||||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
|
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
|
||||||
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */,
|
|
||||||
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */,
|
|
||||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
||||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||||
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */,
|
|
||||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||||
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */,
|
|
||||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||||
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
|
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
|
||||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||||
|
@ -612,8 +500,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */,
|
|
||||||
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -760,7 +646,7 @@
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = Preview;
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
||||||
|
@ -786,7 +672,7 @@
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_ASSET_PATHS = Preview;
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
||||||
|
@ -942,14 +828,6 @@
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/groue/CombineExpectations";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 0.5.0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/onevcat/Kingfisher";
|
repositoryURL = "https://github.com/onevcat/Kingfisher";
|
||||||
|
@ -961,27 +839,22 @@
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
D065F53824D37E5100741304 /* CombineExpectations */ = {
|
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */;
|
productName = PreviewViewModels;
|
||||||
productName = CombineExpectations;
|
|
||||||
};
|
};
|
||||||
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||||
productName = KingfisherSwiftUI;
|
productName = KingfisherSwiftUI;
|
||||||
};
|
};
|
||||||
D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
productName = ServiceLayer;
|
|
||||||
};
|
|
||||||
D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */ = {
|
D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = ServiceLayer;
|
productName = ServiceLayer;
|
||||||
};
|
};
|
||||||
D0E2C1CD24FD7EE900854680 /* ServiceLayerMocks */ = {
|
D0E2C1D024FD97F000854680 /* ViewModels */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = ServiceLayerMocks;
|
productName = ViewModels;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "CombineExpectations",
|
"package": "CombineExpectations",
|
||||||
"repositoryURL": "https://github.com/groue/CombineExpectations",
|
"repositoryURL": "https://github.com/groue/CombineExpectations.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "96d5604151c94b21fbca6877b237e80af9e821dd",
|
"revision": "96d5604151c94b21fbca6877b237e80af9e821dd",
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct AlertItem: Identifiable {
|
|
||||||
let id = UUID()
|
|
||||||
let error: Error
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Combine
|
|
||||||
import HTTP
|
|
||||||
import Mastodon
|
|
||||||
import MastodonStubs
|
|
||||||
import ServiceLayer
|
|
||||||
import ServiceLayerMocks
|
|
||||||
|
|
||||||
// swiftlint:disable force_try
|
|
||||||
private let decoder = APIDecoder()
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
|
||||||
private let devInstanceURL = URL(string: "https://mastodon.social")!
|
|
||||||
private let devIdentityID = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
|
|
||||||
private let devAccessToken = "DEVELOPMENT_ACCESS_TOKEN"
|
|
||||||
|
|
||||||
extension Account {
|
|
||||||
static let development = try! decoder.decode(Account.self,
|
|
||||||
from: AccountEndpoint.verifyCredentials.data(url: devInstanceURL)!)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Instance {
|
|
||||||
static let development = try! decoder.decode(Instance.self,
|
|
||||||
from: InstanceEndpoint.instance.data(url: devInstanceURL)!)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppEnvironment {
|
|
||||||
static let development = AppEnvironment(
|
|
||||||
session: Session(configuration: .stubbing),
|
|
||||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
|
||||||
keychainServiceType: MockKeychainService.self,
|
|
||||||
userDefaults: MockUserDefaults(),
|
|
||||||
inMemoryContent: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AllIdentitiesService {
|
|
||||||
static let fresh = try! AllIdentitiesService(environment: .development)
|
|
||||||
|
|
||||||
static var development: Self = {
|
|
||||||
let allIdentitiesService = try! AllIdentitiesService(environment: .development)
|
|
||||||
|
|
||||||
allIdentitiesService.authorizeIdentity(id: devIdentityID, instanceURL: devInstanceURL)
|
|
||||||
.receive(on: ImmediateScheduler.shared)
|
|
||||||
.sink { _ in } receiveValue: { _ in }
|
|
||||||
.store(in: &cancellables)
|
|
||||||
|
|
||||||
// let identityService = try! allIdentitiesService.identityService(id: devIdentityID)
|
|
||||||
//
|
|
||||||
// identityService.verifyCredentials()
|
|
||||||
// .receive(on: ImmediateScheduler.shared)
|
|
||||||
// .sink { _ in } receiveValue: { _ in }
|
|
||||||
// .store(in: &cancellables)
|
|
||||||
//
|
|
||||||
// identityService.refreshInstance()
|
|
||||||
// .receive(on: ImmediateScheduler.shared)
|
|
||||||
// .sink { _ in } receiveValue: { _ in }
|
|
||||||
// .store(in: &cancellables)
|
|
||||||
|
|
||||||
return allIdentitiesService
|
|
||||||
} ()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IdentityService {
|
|
||||||
static let development = try! AllIdentitiesService.development.identityService(id: devIdentityID)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UserNotificationService {
|
|
||||||
static let development = UserNotificationService(userNotificationCenter: .current())
|
|
||||||
}
|
|
||||||
|
|
||||||
extension RootViewModel {
|
|
||||||
static let development = RootViewModel(
|
|
||||||
appDelegate: AppDelegate(),
|
|
||||||
allIdentitiesService: .development,
|
|
||||||
userNotificationService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AddIdentityViewModel {
|
|
||||||
static let development = RootViewModel.development.addIdentityViewModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TabNavigationViewModel {
|
|
||||||
static let development = RootViewModel.development.tabNavigationViewModel!
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SecondaryNavigationViewModel {
|
|
||||||
static let development = TabNavigationViewModel.development.secondaryNavigationViewModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IdentitiesViewModel {
|
|
||||||
static let development = IdentitiesViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ListsViewModel {
|
|
||||||
static let development = ListsViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PreferencesViewModel {
|
|
||||||
static let development = PreferencesViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension PostingReadingPreferencesViewModel {
|
|
||||||
static let development = PostingReadingPreferencesViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NotificationTypesPreferencesViewModel {
|
|
||||||
static let development = NotificationTypesPreferencesViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FiltersViewModel {
|
|
||||||
static let development = FiltersViewModel(identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EditFilterViewModel {
|
|
||||||
static let development = EditFilterViewModel(filter: Filter.new, identityService: .development)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension StatusListViewModel {
|
|
||||||
static let development = StatusListViewModel(
|
|
||||||
statusListService: IdentityService.development.service(timeline: .home))
|
|
||||||
}
|
|
||||||
|
|
||||||
// swiftlint:enable force_try
|
|
|
@ -11,7 +11,7 @@ public struct AllIdentitiesService {
|
||||||
private let environment: AppEnvironment
|
private let environment: AppEnvironment
|
||||||
|
|
||||||
public init(environment: AppEnvironment) throws {
|
public init(environment: AppEnvironment) throws {
|
||||||
self.identityDatabase = try IdentityDatabase(inMemory: environment.inMemoryContent)
|
self.identityDatabase = try IdentityDatabase(environment: environment)
|
||||||
self.environment = environment
|
self.environment = environment
|
||||||
|
|
||||||
mostRecentlyUsedIdentityID = identityDatabase.mostRecentlyUsedIdentityIDObservation()
|
mostRecentlyUsedIdentityID = identityDatabase.mostRecentlyUsedIdentityIDObservation()
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Mastodon
|
||||||
struct ContentDatabase {
|
struct ContentDatabase {
|
||||||
private let databaseQueue: DatabaseQueue
|
private let databaseQueue: DatabaseQueue
|
||||||
|
|
||||||
init(identityID: UUID, inMemory: Bool) throws {
|
init(identityID: UUID, environment: AppEnvironment) throws {
|
||||||
guard
|
guard
|
||||||
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
||||||
.documentDirectory,
|
.documentDirectory,
|
||||||
|
@ -17,7 +17,7 @@ struct ContentDatabase {
|
||||||
.first
|
.first
|
||||||
else { throw DatabaseError.documentsDirectoryNotFound }
|
else { throw DatabaseError.documentsDirectoryNotFound }
|
||||||
|
|
||||||
if inMemory {
|
if environment.inMemoryContent {
|
||||||
databaseQueue = DatabaseQueue()
|
databaseQueue = DatabaseQueue()
|
||||||
} else {
|
} else {
|
||||||
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/\(identityID.uuidString).sqlite3")
|
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/\(identityID.uuidString).sqlite3")
|
||||||
|
|
|
@ -12,7 +12,7 @@ enum IdentityDatabaseError: Error {
|
||||||
struct IdentityDatabase {
|
struct IdentityDatabase {
|
||||||
private let databaseQueue: DatabaseQueue
|
private let databaseQueue: DatabaseQueue
|
||||||
|
|
||||||
init(inMemory: Bool = false) throws {
|
init(environment: AppEnvironment) throws {
|
||||||
guard
|
guard
|
||||||
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
||||||
.documentDirectory,
|
.documentDirectory,
|
||||||
|
@ -20,13 +20,17 @@ struct IdentityDatabase {
|
||||||
.first
|
.first
|
||||||
else { throw DatabaseError.documentsDirectoryNotFound }
|
else { throw DatabaseError.documentsDirectoryNotFound }
|
||||||
|
|
||||||
if inMemory {
|
if environment.inMemoryContent {
|
||||||
databaseQueue = DatabaseQueue()
|
databaseQueue = DatabaseQueue()
|
||||||
} else {
|
} else {
|
||||||
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/IdentityDatabase.sqlite3")
|
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/IdentityDatabase.sqlite3")
|
||||||
}
|
}
|
||||||
|
|
||||||
try Self.migrate(databaseQueue)
|
try Self.migrate(databaseQueue)
|
||||||
|
|
||||||
|
if let fixture = environment.identityFixture {
|
||||||
|
try populate(fixture: fixture)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +239,24 @@ private extension IdentityDatabase {
|
||||||
|
|
||||||
try migrator.migrate(writer)
|
try migrator.migrate(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func populate(fixture: AppEnvironment.IdentityFixture) throws {
|
||||||
|
_ = createIdentity(id: fixture.id, url: fixture.instanceURL)
|
||||||
|
.receive(on: ImmediateScheduler.shared)
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
|
||||||
|
if let instance = fixture.instance {
|
||||||
|
_ = updateInstance(instance, forIdentityID: fixture.id)
|
||||||
|
.receive(on: ImmediateScheduler.shared)
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
}
|
||||||
|
|
||||||
|
if let account = fixture.account {
|
||||||
|
_ = updateAccount(account, forIdentityID: fixture.id)
|
||||||
|
.receive(on: ImmediateScheduler.shared)
|
||||||
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct StoredIdentity: Codable, Hashable, FetchableRecord, PersistableRecord {
|
private struct StoredIdentity: Codable, Hashable, FetchableRecord, PersistableRecord {
|
||||||
|
|
|
@ -3,32 +3,57 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import HTTP
|
import HTTP
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
public struct AppEnvironment {
|
public 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
|
let userDefaults: UserDefaults
|
||||||
|
let userNotificationClient: UserNotificationClient
|
||||||
let inMemoryContent: Bool
|
let inMemoryContent: Bool
|
||||||
|
let identityFixture: IdentityFixture?
|
||||||
|
|
||||||
public init(session: Session,
|
public init(session: Session,
|
||||||
webAuthSessionType: WebAuthSession.Type,
|
webAuthSessionType: WebAuthSession.Type,
|
||||||
keychainServiceType: KeychainService.Type,
|
keychainServiceType: KeychainService.Type,
|
||||||
userDefaults: UserDefaults,
|
userDefaults: UserDefaults,
|
||||||
inMemoryContent: Bool) {
|
userNotificationClient: UserNotificationClient,
|
||||||
|
inMemoryContent: Bool,
|
||||||
|
identityFixture: IdentityFixture?) {
|
||||||
self.session = session
|
self.session = session
|
||||||
self.webAuthSessionType = webAuthSessionType
|
self.webAuthSessionType = webAuthSessionType
|
||||||
self.keychainServiceType = keychainServiceType
|
self.keychainServiceType = keychainServiceType
|
||||||
self.userDefaults = userDefaults
|
self.userDefaults = userDefaults
|
||||||
|
self.userNotificationClient = userNotificationClient
|
||||||
self.inMemoryContent = inMemoryContent
|
self.inMemoryContent = inMemoryContent
|
||||||
|
self.identityFixture = identityFixture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AppEnvironment {
|
public extension AppEnvironment {
|
||||||
static let live: Self = Self(
|
struct IdentityFixture {
|
||||||
|
public let id: UUID
|
||||||
|
public let instanceURL: URL
|
||||||
|
public let instance: Instance?
|
||||||
|
public let account: Account?
|
||||||
|
|
||||||
|
public init(id: UUID, instanceURL: URL, instance: Instance?, account: Account?) {
|
||||||
|
self.id = id
|
||||||
|
self.instanceURL = instanceURL
|
||||||
|
self.instance = instance
|
||||||
|
self.account = account
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func live(userNotificationCenter: UNUserNotificationCenter) -> Self {
|
||||||
|
Self(
|
||||||
session: Session(configuration: .default),
|
session: Session(configuration: .default),
|
||||||
webAuthSessionType: LiveWebAuthSession.self,
|
webAuthSessionType: LiveWebAuthSession.self,
|
||||||
keychainServiceType: LiveKeychainService.self,
|
keychainServiceType: LiveKeychainService.self,
|
||||||
userDefaults: .standard,
|
userDefaults: .standard,
|
||||||
inMemoryContent: false)
|
userNotificationClient: .live(userNotificationCenter),
|
||||||
|
inMemoryContent: false,
|
||||||
|
identityFixture: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import UserNotifications
|
||||||
|
|
||||||
|
public struct UserNotificationClient {
|
||||||
|
public enum DelegateEvent {
|
||||||
|
case willPresentNotification(UNNotification, completionHandler: (UNNotificationPresentationOptions) -> Void)
|
||||||
|
case didReceiveResponse(UNNotificationResponse, completionHandler: () -> Void)
|
||||||
|
case openSettingsForNotification(UNNotification?)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var getNotificationSettings: (@escaping (UNNotificationSettings) -> Void) -> Void
|
||||||
|
public var requestAuthorization: (UNAuthorizationOptions, @escaping (Bool, Error?) -> Void) -> Void
|
||||||
|
public var delegateEvents: AnyPublisher<DelegateEvent, Never>
|
||||||
|
|
||||||
|
public init(
|
||||||
|
getNotificationSettings: @escaping (@escaping (UNNotificationSettings) -> Void) -> Void,
|
||||||
|
requestAuthorization: @escaping (UNAuthorizationOptions, @escaping (Bool, Error?) -> Void) -> Void,
|
||||||
|
delegateEvents: AnyPublisher<DelegateEvent, Never>) {
|
||||||
|
self.getNotificationSettings = getNotificationSettings
|
||||||
|
self.requestAuthorization = requestAuthorization
|
||||||
|
self.delegateEvents = delegateEvents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UserNotificationClient {
|
||||||
|
public static func live(_ userNotificationCenter: UNUserNotificationCenter) -> Self {
|
||||||
|
// swiftlint:disable nesting
|
||||||
|
class Delegate: NSObject, UNUserNotificationCenterDelegate {
|
||||||
|
let subject: PassthroughSubject<DelegateEvent, Never>
|
||||||
|
|
||||||
|
init(subject: PassthroughSubject<DelegateEvent, Never>) {
|
||||||
|
self.subject = subject
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(
|
||||||
|
_ center: UNUserNotificationCenter,
|
||||||
|
willPresent notification: UNNotification,
|
||||||
|
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||||
|
subject.send(.willPresentNotification(notification, completionHandler: completionHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||||
|
didReceive response: UNNotificationResponse,
|
||||||
|
withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
|
subject.send(.didReceiveResponse(response, completionHandler: completionHandler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||||
|
openSettingsFor notification: UNNotification?) {
|
||||||
|
subject.send(.openSettingsForNotification(notification))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable nesting
|
||||||
|
|
||||||
|
let subject = PassthroughSubject<DelegateEvent, Never>()
|
||||||
|
var delegate: Delegate? = Delegate(subject: subject)
|
||||||
|
userNotificationCenter.delegate = delegate
|
||||||
|
|
||||||
|
return UserNotificationClient(
|
||||||
|
getNotificationSettings: userNotificationCenter.getNotificationSettings,
|
||||||
|
requestAuthorization: userNotificationCenter.requestAuthorization,
|
||||||
|
delegateEvents: subject
|
||||||
|
.handleEvents(receiveCancel: { delegate = nil })
|
||||||
|
.eraseToAnyPublisher())
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,11 @@ extension WebAuthSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
webAuthSession.presentationContextProvider = presentationContextProvider
|
webAuthSession.presentationContextProvider = presentationContextProvider
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
webAuthSession.start()
|
webAuthSession.start()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ public 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)
|
contentDatabase = try ContentDatabase(identityID: identityID, environment: environment)
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -4,15 +4,14 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
public class UserNotificationService: NSObject {
|
public struct UserNotificationService {
|
||||||
private let userNotificationCenter: UNUserNotificationCenter
|
let events: AnyPublisher<UserNotificationClient.DelegateEvent, Never>
|
||||||
|
|
||||||
public init(userNotificationCenter: UNUserNotificationCenter = .current()) {
|
private let userNotificationClient: UserNotificationClient
|
||||||
self.userNotificationCenter = userNotificationCenter
|
|
||||||
|
|
||||||
super.init()
|
public init(environment: AppEnvironment) {
|
||||||
|
self.userNotificationClient = environment.userNotificationClient
|
||||||
userNotificationCenter.delegate = self
|
events = userNotificationClient.delegateEvents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +19,9 @@ public extension UserNotificationService {
|
||||||
func isAuthorized() -> AnyPublisher<Bool, Error> {
|
func isAuthorized() -> AnyPublisher<Bool, Error> {
|
||||||
getNotificationSettings()
|
getNotificationSettings()
|
||||||
.map(\.authorizationStatus)
|
.map(\.authorizationStatus)
|
||||||
.flatMap { [weak self] status -> AnyPublisher<Bool, Error> in
|
.flatMap { status -> AnyPublisher<Bool, Error> in
|
||||||
if status == .notDetermined {
|
if status == .notDetermined {
|
||||||
return self?.requestProvisionalAuthorization()
|
return requestProvisionalAuthorization().eraseToAnyPublisher()
|
||||||
.eraseToAnyPublisher()
|
|
||||||
?? Empty().eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Just(status == .authorized || status == .provisional)
|
return Just(status == .authorized || status == .provisional)
|
||||||
|
@ -37,16 +34,15 @@ public extension UserNotificationService {
|
||||||
|
|
||||||
private extension UserNotificationService {
|
private extension UserNotificationService {
|
||||||
func getNotificationSettings() -> AnyPublisher<UNNotificationSettings, Never> {
|
func getNotificationSettings() -> AnyPublisher<UNNotificationSettings, Never> {
|
||||||
Future<UNNotificationSettings, Never> { [weak self] promise in
|
Future<UNNotificationSettings, Never> { promise in
|
||||||
self?.userNotificationCenter.getNotificationSettings { promise(.success($0)) }
|
userNotificationClient.getNotificationSettings { promise(.success($0)) }
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestProvisionalAuthorization() -> AnyPublisher<Bool, Error> {
|
func requestProvisionalAuthorization() -> AnyPublisher<Bool, Error> {
|
||||||
Future<Bool, Error> { [weak self] promise in
|
Future<Bool, Error> { promise in
|
||||||
self?.userNotificationCenter.requestAuthorization(
|
userNotificationClient.requestAuthorization([.alert, .sound, .badge, .provisional]) { granted, error in
|
||||||
options: [.alert, .sound, .badge, .provisional]) { granted, error in
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
return promise(.failure(error))
|
return promise(.failure(error))
|
||||||
}
|
}
|
||||||
|
@ -57,13 +53,3 @@ private extension UserNotificationService {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserNotificationService: UNUserNotificationCenterDelegate {
|
|
||||||
public func userNotificationCenter(
|
|
||||||
_ center: UNUserNotificationCenter,
|
|
||||||
willPresent notification: UNNotification,
|
|
||||||
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
||||||
print(notification.request.content.body)
|
|
||||||
completionHandler(.banner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import HTTP
|
import HTTP
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
import Stubbing
|
import Stubbing
|
||||||
|
|
||||||
public extension AppEnvironment {
|
public extension AppEnvironment {
|
||||||
static let mock = AppEnvironment(
|
static func mock(identityFixture: IdentityFixture? = nil) -> Self {
|
||||||
|
AppEnvironment(
|
||||||
session: Session(configuration: .stubbing),
|
session: Session(configuration: .stubbing),
|
||||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
||||||
keychainServiceType: MockKeychainService.self,
|
keychainServiceType: MockKeychainService.self,
|
||||||
userDefaults: MockUserDefaults(),
|
userDefaults: MockUserDefaults(),
|
||||||
inMemoryContent: true)
|
userNotificationClient: .mock,
|
||||||
|
inMemoryContent: true,
|
||||||
|
identityFixture: identityFixture)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public extension UserNotificationClient {
|
||||||
|
static let mock = UserNotificationClient(
|
||||||
|
getNotificationSettings: { _ in },
|
||||||
|
requestAuthorization: { _, _ in },
|
||||||
|
delegateEvents: Empty(completeImmediately: false).eraseToAnyPublisher())
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import CombineExpectations
|
||||||
|
|
||||||
class AuthenticationServiceTests: XCTestCase {
|
class AuthenticationServiceTests: XCTestCase {
|
||||||
func testAuthentication() throws {
|
func testAuthentication() throws {
|
||||||
let sut = AuthenticationService(environment: .mock)
|
let sut = AuthenticationService(environment: .mock())
|
||||||
let instanceURL = URL(string: "https://mastodon.social")!
|
let instanceURL = URL(string: "https://mastodon.social")!
|
||||||
let appAuthorizationRecorder = sut.authorizeApp(instanceURL: instanceURL).record()
|
let appAuthorizationRecorder = sut.authorizeApp(instanceURL: instanceURL).record()
|
||||||
let appAuthorization = try wait(for: appAuthorizationRecorder.next(), timeout: 1)
|
let appAuthorization = try wait(for: appAuthorizationRecorder.next(), timeout: 1)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ServiceLayer
|
import ViewModels
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MetatextApp: App {
|
struct MetatextApp: App {
|
||||||
|
@ -12,11 +12,11 @@ struct MetatextApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
RootView(
|
RootView(
|
||||||
viewModel: RootViewModel(appDelegate: appDelegate,
|
|
||||||
// swiftlint:disable force_try
|
// swiftlint:disable force_try
|
||||||
allIdentitiesService: try! AllIdentitiesService(environment: .live),
|
viewModel: try! RootViewModel(
|
||||||
|
environment: .live(userNotificationCenter: .current()),
|
||||||
|
registerForRemoteNotifications: appDelegate.registerForRemoteNotifications))
|
||||||
// swiftlint:enable force_try
|
// swiftlint:enable force_try
|
||||||
userNotificationService: UserNotificationService()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
class StatusListViewController: UITableViewController {
|
class StatusListViewController: UITableViewController {
|
||||||
private let viewModel: StatusListViewModel
|
private let viewModel: StatusListViewModel
|
||||||
|
|
5
ViewModels/.gitignore
vendored
Normal file
5
ViewModels/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
/*.xcodeproj
|
||||||
|
xcuserdata/
|
34
ViewModels/Package.swift
Normal file
34
ViewModels/Package.swift
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// swift-tools-version:5.3
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "ViewModels",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v14),
|
||||||
|
.macOS(.v11)
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
.library(
|
||||||
|
name: "ViewModels",
|
||||||
|
targets: ["ViewModels"]),
|
||||||
|
.library(
|
||||||
|
name: "PreviewViewModels",
|
||||||
|
targets: ["PreviewViewModels"])
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/groue/CombineExpectations.git", .upToNextMajor(from: "0.5.0")),
|
||||||
|
.package(path: "ServiceLayer")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "ViewModels",
|
||||||
|
dependencies: ["ServiceLayer"]),
|
||||||
|
.target(
|
||||||
|
name: "PreviewViewModels",
|
||||||
|
dependencies: ["ViewModels", .product(name: "ServiceLayerMocks", package: "ServiceLayer")]),
|
||||||
|
.testTarget(
|
||||||
|
name: "ViewModelsTests",
|
||||||
|
dependencies: ["CombineExpectations", "PreviewViewModels"])
|
||||||
|
]
|
||||||
|
)
|
103
ViewModels/Sources/PreviewViewModels/ViewModelMocks.swift
Normal file
103
ViewModels/Sources/PreviewViewModels/ViewModelMocks.swift
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import HTTP
|
||||||
|
import Mastodon
|
||||||
|
import MastodonStubs
|
||||||
|
import ServiceLayer
|
||||||
|
import ServiceLayerMocks
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
private let decoder = APIDecoder()
|
||||||
|
private let devInstanceURL = URL(string: "https://mastodon.social")!
|
||||||
|
|
||||||
|
// swiftlint:disable force_try
|
||||||
|
extension AppEnvironment {
|
||||||
|
public static let mockAuthenticated: Self = .mock(
|
||||||
|
identityFixture: .init(
|
||||||
|
id: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!,
|
||||||
|
instanceURL: devInstanceURL,
|
||||||
|
instance: try! decoder.decode(Instance.self,
|
||||||
|
from: InstanceEndpoint.instance.data(url: devInstanceURL)!),
|
||||||
|
account: try! decoder.decode(Account.self,
|
||||||
|
from: AccountEndpoint.verifyCredentials.data(url: devInstanceURL)!)))
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RootViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> Self {
|
||||||
|
try! Self(environment: environment,
|
||||||
|
registerForRemoteNotifications: { Empty().eraseToAnyPublisher() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// swiftlint:enable force_try
|
||||||
|
|
||||||
|
extension AddIdentityViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> AddIdentityViewModel {
|
||||||
|
RootViewModel.mock(environment: environment).addIdentityViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TabNavigationViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> TabNavigationViewModel {
|
||||||
|
RootViewModel.mock(environment: environment).tabNavigationViewModel!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SecondaryNavigationViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> SecondaryNavigationViewModel {
|
||||||
|
TabNavigationViewModel.mock(environment: environment)
|
||||||
|
.secondaryNavigationViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IdentitiesViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> IdentitiesViewModel {
|
||||||
|
SecondaryNavigationViewModel.mock(environment: environment).identitiesViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ListsViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> ListsViewModel {
|
||||||
|
SecondaryNavigationViewModel.mock(environment: environment).listsViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PreferencesViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> PreferencesViewModel {
|
||||||
|
SecondaryNavigationViewModel.mock(environment: environment).preferencesViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PostingReadingPreferencesViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> PostingReadingPreferencesViewModel {
|
||||||
|
PreferencesViewModel.mock(environment: environment)
|
||||||
|
.postingReadingPreferencesViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NotificationTypesPreferencesViewModel {
|
||||||
|
public static func mock(
|
||||||
|
environment: AppEnvironment = .mockAuthenticated) -> NotificationTypesPreferencesViewModel {
|
||||||
|
PreferencesViewModel.mock(environment: environment)
|
||||||
|
.notificationTypesPreferencesViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FiltersViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> FiltersViewModel {
|
||||||
|
PreferencesViewModel.mock(environment: environment).filtersViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EditFilterViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> EditFilterViewModel {
|
||||||
|
FiltersViewModel.mock(environment: environment).editFilterViewModel(filter: .new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusListViewModel {
|
||||||
|
public static func mock(environment: AppEnvironment = .mockAuthenticated) -> StatusListViewModel {
|
||||||
|
TabNavigationViewModel.mock(environment: environment).viewModel(timeline: .home)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,11 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class AddIdentityViewModel: ObservableObject {
|
public class AddIdentityViewModel: ObservableObject {
|
||||||
@Published var urlFieldText = ""
|
@Published public var urlFieldText = ""
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
@Published private(set) var loading = false
|
@Published public private(set) var loading = false
|
||||||
let addedIdentityID: AnyPublisher<UUID, Never>
|
public let addedIdentityID: AnyPublisher<UUID, Never>
|
||||||
|
|
||||||
private let allIdentitiesService: AllIdentitiesService
|
private let allIdentitiesService: AllIdentitiesService
|
||||||
private let addedIdentityIDInput = PassthroughSubject<UUID, Never>()
|
private let addedIdentityIDInput = PassthroughSubject<UUID, Never>()
|
||||||
|
@ -18,7 +18,9 @@ class AddIdentityViewModel: ObservableObject {
|
||||||
self.allIdentitiesService = allIdentitiesService
|
self.allIdentitiesService = allIdentitiesService
|
||||||
addedIdentityID = addedIdentityIDInput.eraseToAnyPublisher()
|
addedIdentityID = addedIdentityIDInput.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension AddIdentityViewModel {
|
||||||
func logInTapped() {
|
func logInTapped() {
|
||||||
let identityID = UUID()
|
let identityID = UUID()
|
||||||
let instanceURL: URL
|
let instanceURL: URL
|
||||||
|
@ -35,8 +37,8 @@ class AddIdentityViewModel: ObservableObject {
|
||||||
.collect()
|
.collect()
|
||||||
.map { _ in (identityID, instanceURL) }
|
.map { _ in (identityID, instanceURL) }
|
||||||
.flatMap(allIdentitiesService.createIdentity(id:instanceURL:))
|
.flatMap(allIdentitiesService.createIdentity(id:instanceURL:))
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.receive(on: RunLoop.main)
|
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
receiveSubscription: { [weak self] _ in self?.loading = true },
|
||||||
receiveCompletion: { [weak self] _ in self?.loading = false })
|
receiveCompletion: { [weak self] _ in self?.loading = false })
|
|
@ -3,15 +3,15 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
|
|
||||||
struct AttachmentViewModel {
|
public struct AttachmentViewModel {
|
||||||
let attachment: Attachment
|
public let attachment: Attachment
|
||||||
|
|
||||||
init(attachment: Attachment) {
|
init(attachment: Attachment) {
|
||||||
self.attachment = attachment
|
self.attachment = attachment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AttachmentViewModel {
|
public extension AttachmentViewModel {
|
||||||
var aspectRatio: Double? {
|
var aspectRatio: Double? {
|
||||||
if
|
if
|
||||||
let info = attachment.meta?.original,
|
let info = attachment.meta?.original,
|
|
@ -5,13 +5,13 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class EditFilterViewModel: ObservableObject {
|
public class EditFilterViewModel: ObservableObject {
|
||||||
@Published var filter: Filter
|
@Published public var filter: Filter
|
||||||
@Published var saving = false
|
@Published public var saving = false
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
let saveCompleted: AnyPublisher<Void, Never>
|
public let saveCompleted: AnyPublisher<Void, Never>
|
||||||
|
|
||||||
var date: Date {
|
public var date: Date {
|
||||||
didSet { filter.expiresAt = date }
|
didSet { filter.expiresAt = date }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class EditFilterViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EditFilterViewModel {
|
public extension EditFilterViewModel {
|
||||||
var isNew: Bool { filter.id == Filter.newFilterID }
|
var isNew: Bool { filter.id == Filter.newFilterID }
|
||||||
|
|
||||||
var isSaveDisabled: Bool { filter.phrase == "" || filter.context.isEmpty }
|
var isSaveDisabled: Bool { filter.phrase == "" || filter.context.isEmpty }
|
8
ViewModels/Sources/ViewModels/Entities/AlertItem.swift
Normal file
8
ViewModels/Sources/ViewModels/Entities/AlertItem.swift
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct AlertItem: Identifiable {
|
||||||
|
public let id = UUID()
|
||||||
|
public let error: Error
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
private static let HTTPSPrefix = "https://"
|
||||||
|
|
||||||
|
func url() throws -> URL {
|
||||||
|
let url: URL?
|
||||||
|
|
||||||
|
if hasPrefix(Self.HTTPSPrefix) {
|
||||||
|
url = URL(string: self)
|
||||||
|
} else {
|
||||||
|
url = URL(string: Self.HTTPSPrefix + self)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let validURL = url else { throw URLError(.badURL) }
|
||||||
|
|
||||||
|
return validURL
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,10 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class FiltersViewModel: ObservableObject {
|
public class FiltersViewModel: ObservableObject {
|
||||||
@Published var activeFilters = [Filter]()
|
@Published public var activeFilters = [Filter]()
|
||||||
@Published var expiredFilters = [Filter]()
|
@Published public var expiredFilters = [Filter]()
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -28,7 +28,7 @@ class FiltersViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FiltersViewModel {
|
public extension FiltersViewModel {
|
||||||
func refreshFilters() {
|
func refreshFilters() {
|
||||||
identityService.refreshFilters()
|
identityService.refreshFilters()
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
@ -4,10 +4,10 @@ import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class IdentitiesViewModel: ObservableObject {
|
public class IdentitiesViewModel: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published public private(set) var identity: Identity
|
||||||
@Published var identities = [Identity]()
|
@Published public var identities = [Identity]()
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
|
@ -5,10 +5,10 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class ListsViewModel: ObservableObject {
|
public class ListsViewModel: ObservableObject {
|
||||||
@Published private(set) var lists = [MastodonList]()
|
@Published public private(set) var lists = [MastodonList]()
|
||||||
@Published private(set) var creatingList = false
|
@Published public private(set) var creatingList = false
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -29,7 +29,7 @@ class ListsViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ListsViewModel {
|
public extension ListsViewModel {
|
||||||
func refreshLists() {
|
func refreshLists() {
|
||||||
identityService.refreshLists()
|
identityService.refreshLists()
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
@ -5,9 +5,9 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class NotificationTypesPreferencesViewModel: ObservableObject {
|
public class NotificationTypesPreferencesViewModel: ObservableObject {
|
||||||
@Published var pushSubscriptionAlerts: PushSubscription.Alerts
|
@Published public var pushSubscriptionAlerts: PushSubscription.Alerts
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
|
@ -4,9 +4,9 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class PostingReadingPreferencesViewModel: ObservableObject {
|
public class PostingReadingPreferencesViewModel: ObservableObject {
|
||||||
@Published var preferences: Identity.Preferences
|
@Published public var preferences: Identity.Preferences
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
|
@ -3,9 +3,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class PreferencesViewModel: ObservableObject {
|
public class PreferencesViewModel: ObservableObject {
|
||||||
let handle: String
|
public let handle: String
|
||||||
let shouldShowNotificationTypePreferences: Bool
|
public let shouldShowNotificationTypePreferences: Bool
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class PreferencesViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreferencesViewModel {
|
public extension PreferencesViewModel {
|
||||||
func postingReadingPreferencesViewModel() -> PostingReadingPreferencesViewModel {
|
func postingReadingPreferencesViewModel() -> PostingReadingPreferencesViewModel {
|
||||||
PostingReadingPreferencesViewModel(identityService: identityService)
|
PostingReadingPreferencesViewModel(identityService: identityService)
|
||||||
}
|
}
|
|
@ -4,23 +4,20 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class RootViewModel: ObservableObject {
|
public final class RootViewModel: ObservableObject {
|
||||||
@Published private(set) var tabNavigationViewModel: TabNavigationViewModel?
|
@Published public private(set) var tabNavigationViewModel: TabNavigationViewModel?
|
||||||
@Published private var mostRecentlyUsedIdentityID: UUID?
|
|
||||||
|
|
||||||
// swiftlint:disable weak_delegate
|
@Published private var mostRecentlyUsedIdentityID: UUID?
|
||||||
private let appDelegate: AppDelegate
|
|
||||||
// swiftlint:enable weak_delegate
|
|
||||||
private let allIdentitiesService: AllIdentitiesService
|
private let allIdentitiesService: AllIdentitiesService
|
||||||
private let userNotificationService: UserNotificationService
|
private let userNotificationService: UserNotificationService
|
||||||
|
private let registerForRemoteNotifications: () -> AnyPublisher<String, Error>
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(appDelegate: AppDelegate,
|
public init(environment: AppEnvironment,
|
||||||
allIdentitiesService: AllIdentitiesService,
|
registerForRemoteNotifications: @escaping () -> AnyPublisher<String, Error>) throws {
|
||||||
userNotificationService: UserNotificationService) {
|
allIdentitiesService = try AllIdentitiesService(environment: environment)
|
||||||
self.appDelegate = appDelegate
|
userNotificationService = UserNotificationService(environment: environment)
|
||||||
self.allIdentitiesService = allIdentitiesService
|
self.registerForRemoteNotifications = registerForRemoteNotifications
|
||||||
self.userNotificationService = userNotificationService
|
|
||||||
|
|
||||||
allIdentitiesService.mostRecentlyUsedIdentityID.assign(to: &$mostRecentlyUsedIdentityID)
|
allIdentitiesService.mostRecentlyUsedIdentityID.assign(to: &$mostRecentlyUsedIdentityID)
|
||||||
|
|
||||||
|
@ -28,7 +25,7 @@ class RootViewModel: ObservableObject {
|
||||||
|
|
||||||
userNotificationService.isAuthorized()
|
userNotificationService.isAuthorized()
|
||||||
.filter { $0 }
|
.filter { $0 }
|
||||||
.zip(appDelegate.registerForRemoteNotifications())
|
.zip(registerForRemoteNotifications())
|
||||||
.map { $1 }
|
.map { $1 }
|
||||||
.flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:))
|
.flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:))
|
||||||
.sink { _ in } receiveValue: { _ in }
|
.sink { _ in } receiveValue: { _ in }
|
||||||
|
@ -36,7 +33,7 @@ class RootViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RootViewModel {
|
public extension RootViewModel {
|
||||||
func newIdentitySelected(id: UUID?) {
|
func newIdentitySelected(id: UUID?) {
|
||||||
guard let id = id else {
|
guard let id = id else {
|
||||||
tabNavigationViewModel = nil
|
tabNavigationViewModel = nil
|
||||||
|
@ -64,7 +61,7 @@ extension RootViewModel {
|
||||||
|
|
||||||
userNotificationService.isAuthorized()
|
userNotificationService.isAuthorized()
|
||||||
.filter { $0 }
|
.filter { $0 }
|
||||||
.zip(appDelegate.registerForRemoteNotifications())
|
.zip(registerForRemoteNotifications())
|
||||||
.filter { identityService.identity.lastRegisteredDeviceToken != $1 }
|
.filter { identityService.identity.lastRegisteredDeviceToken != $1 }
|
||||||
.map { ($1, identityService.identity.pushSubscriptionAlerts) }
|
.map { ($1, identityService.identity.pushSubscriptionAlerts) }
|
||||||
.flatMap(identityService.createPushSubscription(deviceToken:alerts:))
|
.flatMap(identityService.createPushSubscription(deviceToken:alerts:))
|
|
@ -3,8 +3,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class SecondaryNavigationViewModel: ObservableObject {
|
public class SecondaryNavigationViewModel: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published public private(set) var identity: Identity
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
|
|
||||||
init(identityService: IdentityService) {
|
init(identityService: IdentityService) {
|
||||||
|
@ -14,7 +15,7 @@ class SecondaryNavigationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SecondaryNavigationViewModel {
|
public extension SecondaryNavigationViewModel {
|
||||||
func identitiesViewModel() -> IdentitiesViewModel {
|
func identitiesViewModel() -> IdentitiesViewModel {
|
||||||
IdentitiesViewModel(identityService: identityService)
|
IdentitiesViewModel(identityService: identityService)
|
||||||
}
|
}
|
|
@ -5,11 +5,11 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class StatusListViewModel: ObservableObject {
|
public class StatusListViewModel: ObservableObject {
|
||||||
@Published private(set) var statusIDs = [[String]]()
|
@Published public private(set) var statusIDs = [[String]]()
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
@Published private(set) var loading = false
|
@Published public private(set) var loading = false
|
||||||
private(set) var maintainScrollPositionOfStatusID: String?
|
public private(set) var maintainScrollPositionOfStatusID: String?
|
||||||
|
|
||||||
private var statuses = [String: Status]()
|
private var statuses = [String: Status]()
|
||||||
private let statusListService: StatusListService
|
private let statusListService: StatusListService
|
||||||
|
@ -27,19 +27,21 @@ class StatusListViewModel: ObservableObject {
|
||||||
self?.cleanViewModelCache(newStatusSections: $0)
|
self?.cleanViewModelCache(newStatusSections: $0)
|
||||||
self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) })
|
self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) })
|
||||||
})
|
})
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.map { $0.map { $0.map(\.id) } }
|
.map { $0.map { $0.map(\.id) } }
|
||||||
.assign(to: &$statusIDs)
|
.assign(to: &$statusIDs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusListViewModel {
|
public extension StatusListViewModel {
|
||||||
var paginates: Bool { statusListService.paginates }
|
var paginates: Bool { statusListService.paginates }
|
||||||
|
|
||||||
var contextParentID: String? { statusListService.contextParentID }
|
var contextParentID: String? { statusListService.contextParentID }
|
||||||
|
|
||||||
func request(maxID: String? = nil, minID: String? = nil) {
|
func request(maxID: String? = nil, minID: String? = nil) {
|
||||||
statusListService.request(maxID: maxID, minID: minID)
|
statusListService.request(maxID: maxID, minID: minID)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
receiveSubscription: { [weak self] _ in self?.loading = true },
|
|
@ -5,24 +5,24 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
struct StatusViewModel {
|
public struct StatusViewModel {
|
||||||
let content: NSAttributedString
|
public let content: NSAttributedString
|
||||||
let contentEmoji: [Emoji]
|
public let contentEmoji: [Emoji]
|
||||||
let displayName: String
|
public let displayName: String
|
||||||
let displayNameEmoji: [Emoji]
|
public let displayNameEmoji: [Emoji]
|
||||||
let spoilerText: String
|
public let spoilerText: String
|
||||||
let isReblog: Bool
|
public let isReblog: Bool
|
||||||
let rebloggedByDisplayName: String
|
public let rebloggedByDisplayName: String
|
||||||
let rebloggedByDisplayNameEmoji: [Emoji]
|
public let rebloggedByDisplayNameEmoji: [Emoji]
|
||||||
let attachmentViewModels: [AttachmentViewModel]
|
public let attachmentViewModels: [AttachmentViewModel]
|
||||||
let pollOptionTitles: [String]
|
public let pollOptionTitles: [String]
|
||||||
let pollEmoji: [Emoji]
|
public let pollEmoji: [Emoji]
|
||||||
var isPinned = false
|
public var isPinned = false
|
||||||
var isContextParent = false
|
public var isContextParent = false
|
||||||
var isReplyInContext = false
|
public var isReplyInContext = false
|
||||||
var hasReplyFollowing = false
|
public var hasReplyFollowing = false
|
||||||
var sensitiveContentToggled = false
|
public var sensitiveContentToggled = false
|
||||||
let events: AnyPublisher<AnyPublisher<Never, Error>, Never>
|
public let events: AnyPublisher<AnyPublisher<Never, Error>, Never>
|
||||||
|
|
||||||
private let statusService: StatusService
|
private let statusService: StatusService
|
||||||
private let eventsInput = PassthroughSubject<AnyPublisher<Never, Error>, Never>()
|
private let eventsInput = PassthroughSubject<AnyPublisher<Never, Error>, Never>()
|
||||||
|
@ -49,7 +49,7 @@ struct StatusViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusViewModel {
|
public extension StatusViewModel {
|
||||||
var shouldDisplaySensitiveContent: Bool {
|
var shouldDisplaySensitiveContent: Bool {
|
||||||
if statusService.status.displayStatus.sensitive {
|
if statusService.status.displayStatus.sensitive {
|
||||||
return sensitiveContentToggled
|
return sensitiveContentToggled
|
|
@ -5,14 +5,14 @@ import Combine
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
class TabNavigationViewModel: ObservableObject {
|
public class TabNavigationViewModel: ObservableObject {
|
||||||
@Published private(set) var identity: Identity
|
@Published public private(set) var identity: Identity
|
||||||
@Published private(set) var recentIdentities = [Identity]()
|
@Published public private(set) var recentIdentities = [Identity]()
|
||||||
@Published var timeline = Timeline.home
|
@Published public var timeline = Timeline.home
|
||||||
@Published private(set) var timelinesAndLists = Timeline.nonLists
|
@Published public private(set) var timelinesAndLists = Timeline.nonLists
|
||||||
@Published var presentingSecondaryNavigation = false
|
@Published public var presentingSecondaryNavigation = false
|
||||||
@Published var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
var selectedTab: Tab? = .timelines
|
public var selectedTab: Tab? = .timelines
|
||||||
|
|
||||||
private let identityService: IdentityService
|
private let identityService: IdentityService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -33,7 +33,7 @@ class TabNavigationViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TabNavigationViewModel {
|
public extension TabNavigationViewModel {
|
||||||
var timelineSubtitle: String {
|
var timelineSubtitle: String {
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case .home, .list:
|
case .home, .list:
|
||||||
|
@ -93,7 +93,7 @@ extension TabNavigationViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TabNavigationViewModel {
|
public extension TabNavigationViewModel {
|
||||||
enum Tab: CaseIterable {
|
enum Tab: CaseIterable {
|
||||||
case timelines
|
case timelines
|
||||||
case search
|
case search
|
||||||
|
@ -102,26 +102,6 @@ extension TabNavigationViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TabNavigationViewModel.Tab {
|
|
||||||
var title: String {
|
|
||||||
switch self {
|
|
||||||
case .timelines: return "Timelines"
|
|
||||||
case .search: return "Search"
|
|
||||||
case .notifications: return "Notifications"
|
|
||||||
case .messages: return "Messages"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var systemImageName: String {
|
|
||||||
switch self {
|
|
||||||
case .timelines: return "newspaper"
|
|
||||||
case .search: return "magnifyingglass"
|
|
||||||
case .notifications: return "bell"
|
|
||||||
case .messages: return "envelope"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TabNavigationViewModel.Tab: Identifiable {
|
extension TabNavigationViewModel.Tab: Identifiable {
|
||||||
var id: Self { self }
|
public var id: Self { self }
|
||||||
}
|
}
|
|
@ -5,13 +5,13 @@ import Combine
|
||||||
import CombineExpectations
|
import CombineExpectations
|
||||||
import HTTP
|
import HTTP
|
||||||
import Mastodon
|
import Mastodon
|
||||||
@testable import Metatext
|
|
||||||
|
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
import ServiceLayerMocks
|
||||||
|
@testable import ViewModels
|
||||||
|
|
||||||
class AddIdentityViewModelTests: XCTestCase {
|
class AddIdentityViewModelTests: XCTestCase {
|
||||||
func testAddIdentity() throws {
|
func testAddIdentity() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||||
let addedIDRecorder = sut.addedIdentityID.record()
|
let addedIDRecorder = sut.addedIdentityID.record()
|
||||||
|
|
||||||
sut.urlFieldText = "https://mastodon.social"
|
sut.urlFieldText = "https://mastodon.social"
|
||||||
|
@ -21,7 +21,7 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddIdentityWithoutScheme() throws {
|
func testAddIdentityWithoutScheme() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||||
let addedIDRecorder = sut.addedIdentityID.record()
|
let addedIDRecorder = sut.addedIdentityID.record()
|
||||||
|
|
||||||
sut.urlFieldText = "mastodon.social"
|
sut.urlFieldText = "mastodon.social"
|
||||||
|
@ -31,7 +31,7 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInvalidURL() throws {
|
func testInvalidURL() throws {
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||||
let recorder = sut.$alertItem.record()
|
let recorder = sut.$alertItem.record()
|
||||||
|
|
||||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
||||||
|
@ -50,7 +50,9 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
webAuthSessionType: CanceledLoginMockWebAuthSession.self,
|
webAuthSessionType: CanceledLoginMockWebAuthSession.self,
|
||||||
keychainServiceType: MockKeychainService.self,
|
keychainServiceType: MockKeychainService.self,
|
||||||
userDefaults: MockUserDefaults(),
|
userDefaults: MockUserDefaults(),
|
||||||
inMemoryContent: true)
|
userNotificationClient: .mock,
|
||||||
|
inMemoryContent: true,
|
||||||
|
identityFixture: nil)
|
||||||
let allIdentitiesService = try AllIdentitiesService(environment: environment)
|
let allIdentitiesService = try AllIdentitiesService(environment: environment)
|
||||||
let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService)
|
let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService)
|
||||||
let recorder = sut.$alertItem.record()
|
let recorder = sut.$alertItem.record()
|
||||||
|
@ -62,8 +64,4 @@ class AddIdentityViewModelTests: XCTestCase {
|
||||||
|
|
||||||
try wait(for: recorder.next().inverted, timeout: 1)
|
try wait(for: recorder.next().inverted, timeout: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFuck() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -4,15 +4,16 @@ import XCTest
|
||||||
import Combine
|
import Combine
|
||||||
import CombineExpectations
|
import CombineExpectations
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
@testable import Metatext
|
import ServiceLayerMocks
|
||||||
|
@testable import ViewModels
|
||||||
|
|
||||||
class RootViewModelTests: XCTestCase {
|
class RootViewModelTests: XCTestCase {
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
func testAddIdentity() throws {
|
func testAddIdentity() throws {
|
||||||
let sut = RootViewModel(appDelegate: AppDelegate(),
|
let sut = try RootViewModel(
|
||||||
allIdentitiesService: .fresh,
|
environment: .mock(),
|
||||||
userNotificationService: UserNotificationService())
|
registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() })
|
||||||
let recorder = sut.$tabNavigationViewModel.record()
|
let recorder = sut.$tabNavigationViewModel.record()
|
||||||
|
|
||||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct AddIdentityView: View {
|
struct AddIdentityView: View {
|
||||||
@StateObject var viewModel: AddIdentityViewModel
|
@StateObject var viewModel: AddIdentityViewModel
|
||||||
|
@ -40,9 +41,11 @@ extension AddIdentityView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct AddAccountView_Previews: PreviewProvider {
|
struct AddAccountView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddIdentityView(viewModel: .development)
|
AddIdentityView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
class AttachmentView: UIView {
|
class AttachmentView: UIView {
|
||||||
let imageView = AnimatedImageView()
|
let imageView = AnimatedImageView()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
class AttachmentsView: UIView {
|
class AttachmentsView: UIView {
|
||||||
private let containerStackView = UIStackView()
|
private let containerStackView = UIStackView()
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import struct Mastodon.Filter
|
import struct Mastodon.Filter
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct EditFilterView: View {
|
struct EditFilterView: View {
|
||||||
@StateObject var viewModel: EditFilterViewModel
|
@StateObject var viewModel: EditFilterViewModel
|
||||||
|
@ -96,9 +97,11 @@ extension Filter.Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct EditFilterView_Previews: PreviewProvider {
|
struct EditFilterView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
EditFilterView(viewModel: .development)
|
EditFilterView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import struct Mastodon.Filter
|
import struct Mastodon.Filter
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct FiltersView: View {
|
struct FiltersView: View {
|
||||||
@StateObject var viewModel: FiltersViewModel
|
@StateObject var viewModel: FiltersViewModel
|
||||||
|
@ -55,9 +56,11 @@ private extension FiltersView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct FiltersView_Previews: PreviewProvider {
|
struct FiltersView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
FiltersView(viewModel: .development)
|
FiltersView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import KingfisherSwiftUI
|
import KingfisherSwiftUI
|
||||||
import struct ServiceLayer.Identity
|
import ViewModels
|
||||||
|
|
||||||
struct IdentitiesView: View {
|
struct IdentitiesView: View {
|
||||||
@StateObject var viewModel: IdentitiesViewModel
|
@StateObject var viewModel: IdentitiesViewModel
|
||||||
|
@ -68,10 +68,12 @@ struct IdentitiesView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct IdentitiesView_Previews: PreviewProvider {
|
struct IdentitiesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
IdentitiesView(viewModel: .development)
|
IdentitiesView(viewModel: .mock())
|
||||||
.environmentObject(RootViewModel.development)
|
.environmentObject(RootViewModel.mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct ListsView: View {
|
struct ListsView: View {
|
||||||
@StateObject var viewModel: ListsViewModel
|
@StateObject var viewModel: ListsViewModel
|
||||||
|
@ -55,10 +56,12 @@ struct ListsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct ListsView_Previews: PreviewProvider {
|
struct ListsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ListsView(viewModel: .development)
|
ListsView(viewModel: .mock())
|
||||||
.environmentObject(TabNavigationViewModel.development)
|
.environmentObject(TabNavigationViewModel.mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct NotificationTypesPreferencesView: View {
|
struct NotificationTypesPreferencesView: View {
|
||||||
@StateObject var viewModel: NotificationTypesPreferencesViewModel
|
@StateObject var viewModel: NotificationTypesPreferencesViewModel
|
||||||
|
@ -24,9 +25,11 @@ struct NotificationTypesPreferencesView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct NotificationTypesPreferencesView_Previews: PreviewProvider {
|
struct NotificationTypesPreferencesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NotificationTypesPreferencesView(viewModel: .development)
|
NotificationTypesPreferencesView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import class Mastodon.Status
|
import class Mastodon.Status
|
||||||
import struct Mastodon.Preferences
|
import struct Mastodon.Preferences
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct PostingReadingPreferencesView: View {
|
struct PostingReadingPreferencesView: View {
|
||||||
@StateObject var viewModel: PostingReadingPreferencesViewModel
|
@StateObject var viewModel: PostingReadingPreferencesViewModel
|
||||||
|
@ -50,9 +51,11 @@ struct PostingReadingPreferencesView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct PostingReadingPreferencesViewView_Previews: PreviewProvider {
|
struct PostingReadingPreferencesViewView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PostingReadingPreferencesView(viewModel: .development)
|
PostingReadingPreferencesView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct PreferencesView: View {
|
struct PreferencesView: View {
|
||||||
@StateObject var viewModel: PreferencesViewModel
|
@StateObject var viewModel: PreferencesViewModel
|
||||||
|
@ -26,9 +27,11 @@ struct PreferencesView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct PreferencesView_Previews: PreviewProvider {
|
struct PreferencesView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PreferencesView(viewModel: .development)
|
PreferencesView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct RootView: View {
|
struct RootView: View {
|
||||||
@StateObject var viewModel: RootViewModel
|
@StateObject var viewModel: RootViewModel
|
||||||
|
@ -20,9 +21,11 @@ struct RootView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
RootView(viewModel: .development)
|
RootView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import KingfisherSwiftUI
|
import KingfisherSwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct SecondaryNavigationView: View {
|
struct SecondaryNavigationView: View {
|
||||||
@StateObject var viewModel: SecondaryNavigationViewModel
|
@StateObject var viewModel: SecondaryNavigationViewModel
|
||||||
|
@ -66,11 +67,13 @@ struct SecondaryNavigationView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct SecondaryNavigationView_Previews: PreviewProvider {
|
struct SecondaryNavigationView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
SecondaryNavigationView(viewModel: .development)
|
SecondaryNavigationView(viewModel: .mock())
|
||||||
.environmentObject(RootViewModel.development)
|
.environmentObject(RootViewModel.mock())
|
||||||
.environmentObject(TabNavigationViewModel.development)
|
.environmentObject(TabNavigationViewModel.mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import AVKit
|
import AVKit
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
protocol StatusTableViewCellDelegate: class {
|
protocol StatusTableViewCellDelegate: class {
|
||||||
func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell)
|
func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct StatusListView: UIViewControllerRepresentable {
|
struct StatusListView: UIViewControllerRepresentable {
|
||||||
let viewModel: StatusListViewModel
|
let viewModel: StatusListViewModel
|
||||||
|
@ -15,9 +16,11 @@ struct StatusListView: UIViewControllerRepresentable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct StatusListView_Previews: PreviewProvider {
|
struct StatusListView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
StatusListView(viewModel: .development)
|
StatusListView(viewModel: .mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import KingfisherSwiftUI
|
import KingfisherSwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
struct TabNavigationView: View {
|
struct TabNavigationView: View {
|
||||||
@ObservedObject var viewModel: TabNavigationViewModel
|
@ObservedObject var viewModel: TabNavigationViewModel
|
||||||
|
@ -118,11 +119,33 @@ private extension TabNavigationView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TabNavigationViewModel.Tab {
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .timelines: return "Timelines"
|
||||||
|
case .search: return "Search"
|
||||||
|
case .notifications: return "Notifications"
|
||||||
|
case .messages: return "Messages"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemImageName: String {
|
||||||
|
switch self {
|
||||||
|
case .timelines: return "newspaper"
|
||||||
|
case .search: return "magnifyingglass"
|
||||||
|
case .notifications: return "bell"
|
||||||
|
case .messages: return "envelope"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
struct TabNavigation_Previews: PreviewProvider {
|
struct TabNavigation_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TabNavigationView(viewModel: .development)
|
TabNavigationView(viewModel: .mock())
|
||||||
.environmentObject(RootViewModel.development)
|
.environmentObject(RootViewModel.mock())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue