mirror of
https://github.com/metabolist/metatext.git
synced 2024-12-30 17:20:37 +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
|
||||
|
||||
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 {
|
||||
let countRange = (self as NSString).range(of: String.localizedStringWithFormat("%ld", count))
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
extension View {
|
||||
func alertItem(_ alertItem: Binding<AlertItem?>) -> some View {
|
||||
|
|
|
@ -52,6 +52,6 @@ private extension Client {
|
|||
|
||||
return session.request(target)
|
||||
.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
|
||||
let url = request.url,
|
||||
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 {
|
||||
|
|
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 = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0175CAB24FE2D6300B085F6 /* PreviewViewModels */; };
|
||||
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; };
|
||||
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.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 */; };
|
||||
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 */; };
|
||||
D0BDF66724FD7CDA00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */; };
|
||||
D0BDF66B24FD7CEC00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */; };
|
||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.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 */; };
|
||||
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 */; };
|
||||
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */; };
|
||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.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 */; };
|
||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.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 */; };
|
||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
|
||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
|
||||
D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45724F76169001EBDBB /* Localizable.strings */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
|
@ -102,22 +81,16 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterViewModel.swift; 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -169,9 +129,9 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0BDF66724FD7CDA00C7FA1C /* ServiceLayer in Frameworks */,
|
||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
|
||||
D0E2C1CE24FD7EE900854680 /* ServiceLayerMocks in Frameworks */,
|
||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */,
|
||||
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -179,7 +139,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -222,16 +181,14 @@
|
|||
D0BFDAF524FC7C5300C86618 /* HTTP */,
|
||||
D0C7D45624F76169001EBDBB /* Localizations */,
|
||||
D0E0F1E424FC49FC002C04BF /* Mastodon */,
|
||||
D0C7D43824F76169001EBDBB /* Model */,
|
||||
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
|
||||
D0ED1BB224CE3A1600B4899C /* Preview */,
|
||||
D047FA8D24C3E21200AF17C5 /* Products */,
|
||||
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */,
|
||||
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
||||
D0C7D45324F76169001EBDBB /* System */,
|
||||
D0666A2224C677B400F3F04B /* Tests */,
|
||||
D0C7D43024F76169001EBDBB /* View Controllers */,
|
||||
D0C7D45924F76169001EBDBB /* View Models */,
|
||||
D0E2C1CF24FD8BA400854680 /* ViewModels */,
|
||||
D0C7D42024F76169001EBDBB /* Views */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
|
@ -250,7 +207,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D0666A2524C677B400F3F04B /* Info.plist */,
|
||||
D0ED1B6C24CE0EED00B4899C /* View Models */,
|
||||
);
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -302,14 +258,6 @@
|
|||
path = "View Controllers";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0C7D43824F76169001EBDBB /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0C7D45024F76169001EBDBB /* AlertItem.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0C7D45324F76169001EBDBB /* System */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -328,35 +276,12 @@
|
|||
path = Localizations;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
|
||||
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */,
|
||||
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
|
||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||
D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */,
|
||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */,
|
||||
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */,
|
||||
|
@ -374,23 +299,6 @@
|
|||
path = "Notification Service Extension";
|
||||
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 */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
@ -412,8 +320,8 @@
|
|||
name = Metatext;
|
||||
packageProductDependencies = (
|
||||
D06B492224D4611300642749 /* KingfisherSwiftUI */,
|
||||
D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */,
|
||||
D0E2C1CD24FD7EE900854680 /* ServiceLayerMocks */,
|
||||
D0E2C1D024FD97F000854680 /* ViewModels */,
|
||||
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */,
|
||||
);
|
||||
productName = "Metatext (iOS)";
|
||||
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
|
||||
|
@ -434,7 +342,6 @@
|
|||
);
|
||||
name = Tests;
|
||||
packageProductDependencies = (
|
||||
D065F53824D37E5100741304 /* CombineExpectations */,
|
||||
);
|
||||
productName = "Unit Tests";
|
||||
productReference = D0666A2124C677B400F3F04B /* Tests.xctest */;
|
||||
|
@ -493,7 +400,6 @@
|
|||
);
|
||||
mainGroup = D047FA7F24C3E21000AF17C5;
|
||||
packageReferences = (
|
||||
D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */,
|
||||
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */,
|
||||
);
|
||||
productRefGroup = D047FA8D24C3E21200AF17C5 /* Products */;
|
||||
|
@ -560,47 +466,29 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */,
|
||||
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */,
|
||||
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
|
||||
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
|
||||
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */,
|
||||
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */,
|
||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
|
||||
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
|
||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.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 */,
|
||||
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
|
||||
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */,
|
||||
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
|
||||
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */,
|
||||
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
|
||||
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */,
|
||||
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
|
||||
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */,
|
||||
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
|
||||
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */,
|
||||
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */,
|
||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
|
||||
D04FD74224D4AA34007D572D /* PreviewMocks.swift in Sources */,
|
||||
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
|
||||
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */,
|
||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
|
||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
|
||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
|
||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
|
||||
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */,
|
||||
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */,
|
||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
|
||||
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
|
||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
|
||||
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */,
|
||||
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
|
||||
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */,
|
||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
|
||||
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
|
||||
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
|
||||
|
@ -612,8 +500,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */,
|
||||
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -760,7 +646,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = Preview;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
||||
|
@ -786,7 +672,7 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = Preview;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = 82HL67AXQ2;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = "Supporting Files/Info.plist";
|
||||
|
@ -942,14 +828,6 @@
|
|||
/* End XCConfigurationList 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" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/onevcat/Kingfisher";
|
||||
|
@ -961,27 +839,22 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D065F53824D37E5100741304 /* CombineExpectations */ = {
|
||||
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */;
|
||||
productName = CombineExpectations;
|
||||
productName = PreviewViewModels;
|
||||
};
|
||||
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = KingfisherSwiftUI;
|
||||
};
|
||||
D0BDF66624FD7CDA00C7FA1C /* ServiceLayer */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ServiceLayer;
|
||||
};
|
||||
D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ServiceLayer;
|
||||
};
|
||||
D0E2C1CD24FD7EE900854680 /* ServiceLayerMocks */ = {
|
||||
D0E2C1D024FD97F000854680 /* ViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ServiceLayerMocks;
|
||||
productName = ViewModels;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
{
|
||||
"package": "CombineExpectations",
|
||||
"repositoryURL": "https://github.com/groue/CombineExpectations",
|
||||
"repositoryURL": "https://github.com/groue/CombineExpectations.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"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
|
||||
|
||||
public init(environment: AppEnvironment) throws {
|
||||
self.identityDatabase = try IdentityDatabase(inMemory: environment.inMemoryContent)
|
||||
self.identityDatabase = try IdentityDatabase(environment: environment)
|
||||
self.environment = environment
|
||||
|
||||
mostRecentlyUsedIdentityID = identityDatabase.mostRecentlyUsedIdentityIDObservation()
|
||||
|
|
|
@ -9,7 +9,7 @@ import Mastodon
|
|||
struct ContentDatabase {
|
||||
private let databaseQueue: DatabaseQueue
|
||||
|
||||
init(identityID: UUID, inMemory: Bool) throws {
|
||||
init(identityID: UUID, environment: AppEnvironment) throws {
|
||||
guard
|
||||
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
||||
.documentDirectory,
|
||||
|
@ -17,7 +17,7 @@ struct ContentDatabase {
|
|||
.first
|
||||
else { throw DatabaseError.documentsDirectoryNotFound }
|
||||
|
||||
if inMemory {
|
||||
if environment.inMemoryContent {
|
||||
databaseQueue = DatabaseQueue()
|
||||
} else {
|
||||
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/\(identityID.uuidString).sqlite3")
|
||||
|
|
|
@ -12,7 +12,7 @@ enum IdentityDatabaseError: Error {
|
|||
struct IdentityDatabase {
|
||||
private let databaseQueue: DatabaseQueue
|
||||
|
||||
init(inMemory: Bool = false) throws {
|
||||
init(environment: AppEnvironment) throws {
|
||||
guard
|
||||
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
|
||||
.documentDirectory,
|
||||
|
@ -20,13 +20,17 @@ struct IdentityDatabase {
|
|||
.first
|
||||
else { throw DatabaseError.documentsDirectoryNotFound }
|
||||
|
||||
if inMemory {
|
||||
if environment.inMemoryContent {
|
||||
databaseQueue = DatabaseQueue()
|
||||
} else {
|
||||
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/IdentityDatabase.sqlite3")
|
||||
}
|
||||
|
||||
try Self.migrate(databaseQueue)
|
||||
|
||||
if let fixture = environment.identityFixture {
|
||||
try populate(fixture: fixture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,6 +239,24 @@ private extension IdentityDatabase {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -3,32 +3,57 @@
|
|||
import Foundation
|
||||
import HTTP
|
||||
import Mastodon
|
||||
import UserNotifications
|
||||
|
||||
public struct AppEnvironment {
|
||||
let session: Session
|
||||
let webAuthSessionType: WebAuthSession.Type
|
||||
let keychainServiceType: KeychainService.Type
|
||||
let userDefaults: UserDefaults
|
||||
let userNotificationClient: UserNotificationClient
|
||||
let inMemoryContent: Bool
|
||||
let identityFixture: IdentityFixture?
|
||||
|
||||
public init(session: Session,
|
||||
webAuthSessionType: WebAuthSession.Type,
|
||||
keychainServiceType: KeychainService.Type,
|
||||
userDefaults: UserDefaults,
|
||||
inMemoryContent: Bool) {
|
||||
userNotificationClient: UserNotificationClient,
|
||||
inMemoryContent: Bool,
|
||||
identityFixture: IdentityFixture?) {
|
||||
self.session = session
|
||||
self.webAuthSessionType = webAuthSessionType
|
||||
self.keychainServiceType = keychainServiceType
|
||||
self.userDefaults = userDefaults
|
||||
self.userNotificationClient = userNotificationClient
|
||||
self.inMemoryContent = inMemoryContent
|
||||
self.identityFixture = identityFixture
|
||||
}
|
||||
}
|
||||
|
||||
public extension AppEnvironment {
|
||||
static let live: Self = Self(
|
||||
session: Session(configuration: .default),
|
||||
webAuthSessionType: LiveWebAuthSession.self,
|
||||
keychainServiceType: LiveKeychainService.self,
|
||||
userDefaults: .standard,
|
||||
inMemoryContent: false)
|
||||
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),
|
||||
webAuthSessionType: LiveWebAuthSession.self,
|
||||
keychainServiceType: LiveKeychainService.self,
|
||||
userDefaults: .standard,
|
||||
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,7 +29,10 @@ extension WebAuthSession {
|
|||
}
|
||||
|
||||
webAuthSession.presentationContextProvider = presentationContextProvider
|
||||
webAuthSession.start()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
webAuthSession.start()
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class IdentityService {
|
|||
networkClient.instanceURL = identity.url
|
||||
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
|
||||
self?.observationErrorsInput.send(error)
|
||||
|
|
|
@ -4,15 +4,14 @@ import Foundation
|
|||
import Combine
|
||||
import UserNotifications
|
||||
|
||||
public class UserNotificationService: NSObject {
|
||||
private let userNotificationCenter: UNUserNotificationCenter
|
||||
public struct UserNotificationService {
|
||||
let events: AnyPublisher<UserNotificationClient.DelegateEvent, Never>
|
||||
|
||||
public init(userNotificationCenter: UNUserNotificationCenter = .current()) {
|
||||
self.userNotificationCenter = userNotificationCenter
|
||||
private let userNotificationClient: UserNotificationClient
|
||||
|
||||
super.init()
|
||||
|
||||
userNotificationCenter.delegate = self
|
||||
public init(environment: AppEnvironment) {
|
||||
self.userNotificationClient = environment.userNotificationClient
|
||||
events = userNotificationClient.delegateEvents
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,11 +19,9 @@ public extension UserNotificationService {
|
|||
func isAuthorized() -> AnyPublisher<Bool, Error> {
|
||||
getNotificationSettings()
|
||||
.map(\.authorizationStatus)
|
||||
.flatMap { [weak self] status -> AnyPublisher<Bool, Error> in
|
||||
.flatMap { status -> AnyPublisher<Bool, Error> in
|
||||
if status == .notDetermined {
|
||||
return self?.requestProvisionalAuthorization()
|
||||
.eraseToAnyPublisher()
|
||||
?? Empty().eraseToAnyPublisher()
|
||||
return requestProvisionalAuthorization().eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Just(status == .authorized || status == .provisional)
|
||||
|
@ -37,16 +34,15 @@ public extension UserNotificationService {
|
|||
|
||||
private extension UserNotificationService {
|
||||
func getNotificationSettings() -> AnyPublisher<UNNotificationSettings, Never> {
|
||||
Future<UNNotificationSettings, Never> { [weak self] promise in
|
||||
self?.userNotificationCenter.getNotificationSettings { promise(.success($0)) }
|
||||
Future<UNNotificationSettings, Never> { promise in
|
||||
userNotificationClient.getNotificationSettings { promise(.success($0)) }
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func requestProvisionalAuthorization() -> AnyPublisher<Bool, Error> {
|
||||
Future<Bool, Error> { [weak self] promise in
|
||||
self?.userNotificationCenter.requestAuthorization(
|
||||
options: [.alert, .sound, .badge, .provisional]) { granted, error in
|
||||
Future<Bool, Error> { promise in
|
||||
userNotificationClient.requestAuthorization([.alert, .sound, .badge, .provisional]) { granted, error in
|
||||
if let error = error {
|
||||
return promise(.failure(error))
|
||||
}
|
||||
|
@ -57,13 +53,3 @@ private extension UserNotificationService {
|
|||
.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 HTTP
|
||||
import ServiceLayer
|
||||
import Stubbing
|
||||
|
||||
public extension AppEnvironment {
|
||||
static let mock = AppEnvironment(
|
||||
session: Session(configuration: .stubbing),
|
||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
||||
keychainServiceType: MockKeychainService.self,
|
||||
userDefaults: MockUserDefaults(),
|
||||
inMemoryContent: true)
|
||||
static func mock(identityFixture: IdentityFixture? = nil) -> Self {
|
||||
AppEnvironment(
|
||||
session: Session(configuration: .stubbing),
|
||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
||||
keychainServiceType: MockKeychainService.self,
|
||||
userDefaults: MockUserDefaults(),
|
||||
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 {
|
||||
func testAuthentication() throws {
|
||||
let sut = AuthenticationService(environment: .mock)
|
||||
let sut = AuthenticationService(environment: .mock())
|
||||
let instanceURL = URL(string: "https://mastodon.social")!
|
||||
let appAuthorizationRecorder = sut.authorizeApp(instanceURL: instanceURL).record()
|
||||
let appAuthorization = try wait(for: appAuthorizationRecorder.next(), timeout: 1)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ServiceLayer
|
||||
import ViewModels
|
||||
|
||||
@main
|
||||
struct MetatextApp: App {
|
||||
|
@ -12,11 +12,11 @@ struct MetatextApp: App {
|
|||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView(
|
||||
viewModel: RootViewModel(appDelegate: appDelegate,
|
||||
// swiftlint:disable force_try
|
||||
allIdentitiesService: try! AllIdentitiesService(environment: .live),
|
||||
// swiftlint:enable force_try
|
||||
userNotificationService: UserNotificationService()))
|
||||
// swiftlint:disable force_try
|
||||
viewModel: try! RootViewModel(
|
||||
environment: .live(userNotificationCenter: .current()),
|
||||
registerForRemoteNotifications: appDelegate.registerForRemoteNotifications))
|
||||
// swiftlint:enable force_try
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import ViewModels
|
||||
|
||||
class StatusListViewController: UITableViewController {
|
||||
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 ServiceLayer
|
||||
|
||||
class AddIdentityViewModel: ObservableObject {
|
||||
@Published var urlFieldText = ""
|
||||
@Published var alertItem: AlertItem?
|
||||
@Published private(set) var loading = false
|
||||
let addedIdentityID: AnyPublisher<UUID, Never>
|
||||
public class AddIdentityViewModel: ObservableObject {
|
||||
@Published public var urlFieldText = ""
|
||||
@Published public var alertItem: AlertItem?
|
||||
@Published public private(set) var loading = false
|
||||
public let addedIdentityID: AnyPublisher<UUID, Never>
|
||||
|
||||
private let allIdentitiesService: AllIdentitiesService
|
||||
private let addedIdentityIDInput = PassthroughSubject<UUID, Never>()
|
||||
|
@ -18,7 +18,9 @@ class AddIdentityViewModel: ObservableObject {
|
|||
self.allIdentitiesService = allIdentitiesService
|
||||
addedIdentityID = addedIdentityIDInput.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
public extension AddIdentityViewModel {
|
||||
func logInTapped() {
|
||||
let identityID = UUID()
|
||||
let instanceURL: URL
|
||||
|
@ -35,8 +37,8 @@ class AddIdentityViewModel: ObservableObject {
|
|||
.collect()
|
||||
.map { _ in (identityID, instanceURL) }
|
||||
.flatMap(allIdentitiesService.createIdentity(id:instanceURL:))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.receive(on: RunLoop.main)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
||||
receiveCompletion: { [weak self] _ in self?.loading = false })
|
|
@ -3,15 +3,15 @@
|
|||
import Foundation
|
||||
import Mastodon
|
||||
|
||||
struct AttachmentViewModel {
|
||||
let attachment: Attachment
|
||||
public struct AttachmentViewModel {
|
||||
public let attachment: Attachment
|
||||
|
||||
init(attachment: Attachment) {
|
||||
self.attachment = attachment
|
||||
}
|
||||
}
|
||||
|
||||
extension AttachmentViewModel {
|
||||
public extension AttachmentViewModel {
|
||||
var aspectRatio: Double? {
|
||||
if
|
||||
let info = attachment.meta?.original,
|
|
@ -5,13 +5,13 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
class EditFilterViewModel: ObservableObject {
|
||||
@Published var filter: Filter
|
||||
@Published var saving = false
|
||||
@Published var alertItem: AlertItem?
|
||||
let saveCompleted: AnyPublisher<Void, Never>
|
||||
public class EditFilterViewModel: ObservableObject {
|
||||
@Published public var filter: Filter
|
||||
@Published public var saving = false
|
||||
@Published public var alertItem: AlertItem?
|
||||
public let saveCompleted: AnyPublisher<Void, Never>
|
||||
|
||||
var date: Date {
|
||||
public var date: 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 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 ServiceLayer
|
||||
|
||||
class FiltersViewModel: ObservableObject {
|
||||
@Published var activeFilters = [Filter]()
|
||||
@Published var expiredFilters = [Filter]()
|
||||
@Published var alertItem: AlertItem?
|
||||
public class FiltersViewModel: ObservableObject {
|
||||
@Published public var activeFilters = [Filter]()
|
||||
@Published public var expiredFilters = [Filter]()
|
||||
@Published public var alertItem: AlertItem?
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
@ -28,7 +28,7 @@ class FiltersViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension FiltersViewModel {
|
||||
public extension FiltersViewModel {
|
||||
func refreshFilters() {
|
||||
identityService.refreshFilters()
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
@ -4,10 +4,10 @@ import Combine
|
|||
import Foundation
|
||||
import ServiceLayer
|
||||
|
||||
class IdentitiesViewModel: ObservableObject {
|
||||
@Published private(set) var identity: Identity
|
||||
@Published var identities = [Identity]()
|
||||
@Published var alertItem: AlertItem?
|
||||
public class IdentitiesViewModel: ObservableObject {
|
||||
@Published public private(set) var identity: Identity
|
||||
@Published public var identities = [Identity]()
|
||||
@Published public var alertItem: AlertItem?
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
|
@ -5,10 +5,10 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
class ListsViewModel: ObservableObject {
|
||||
@Published private(set) var lists = [MastodonList]()
|
||||
@Published private(set) var creatingList = false
|
||||
@Published var alertItem: AlertItem?
|
||||
public class ListsViewModel: ObservableObject {
|
||||
@Published public private(set) var lists = [MastodonList]()
|
||||
@Published public private(set) var creatingList = false
|
||||
@Published public var alertItem: AlertItem?
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
@ -29,7 +29,7 @@ class ListsViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension ListsViewModel {
|
||||
public extension ListsViewModel {
|
||||
func refreshLists() {
|
||||
identityService.refreshLists()
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
|
@ -5,9 +5,9 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
class NotificationTypesPreferencesViewModel: ObservableObject {
|
||||
@Published var pushSubscriptionAlerts: PushSubscription.Alerts
|
||||
@Published var alertItem: AlertItem?
|
||||
public class NotificationTypesPreferencesViewModel: ObservableObject {
|
||||
@Published public var pushSubscriptionAlerts: PushSubscription.Alerts
|
||||
@Published public var alertItem: AlertItem?
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
|
@ -4,9 +4,9 @@ import Foundation
|
|||
import Combine
|
||||
import ServiceLayer
|
||||
|
||||
class PostingReadingPreferencesViewModel: ObservableObject {
|
||||
@Published var preferences: Identity.Preferences
|
||||
@Published var alertItem: AlertItem?
|
||||
public class PostingReadingPreferencesViewModel: ObservableObject {
|
||||
@Published public var preferences: Identity.Preferences
|
||||
@Published public var alertItem: AlertItem?
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
|
@ -3,9 +3,9 @@
|
|||
import Foundation
|
||||
import ServiceLayer
|
||||
|
||||
class PreferencesViewModel: ObservableObject {
|
||||
let handle: String
|
||||
let shouldShowNotificationTypePreferences: Bool
|
||||
public class PreferencesViewModel: ObservableObject {
|
||||
public let handle: String
|
||||
public let shouldShowNotificationTypePreferences: Bool
|
||||
|
||||
private let identityService: IdentityService
|
||||
|
||||
|
@ -17,7 +17,7 @@ class PreferencesViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension PreferencesViewModel {
|
||||
public extension PreferencesViewModel {
|
||||
func postingReadingPreferencesViewModel() -> PostingReadingPreferencesViewModel {
|
||||
PostingReadingPreferencesViewModel(identityService: identityService)
|
||||
}
|
|
@ -4,23 +4,20 @@ import Foundation
|
|||
import Combine
|
||||
import ServiceLayer
|
||||
|
||||
class RootViewModel: ObservableObject {
|
||||
@Published private(set) var tabNavigationViewModel: TabNavigationViewModel?
|
||||
@Published private var mostRecentlyUsedIdentityID: UUID?
|
||||
public final class RootViewModel: ObservableObject {
|
||||
@Published public private(set) var tabNavigationViewModel: TabNavigationViewModel?
|
||||
|
||||
// swiftlint:disable weak_delegate
|
||||
private let appDelegate: AppDelegate
|
||||
// swiftlint:enable weak_delegate
|
||||
@Published private var mostRecentlyUsedIdentityID: UUID?
|
||||
private let allIdentitiesService: AllIdentitiesService
|
||||
private let userNotificationService: UserNotificationService
|
||||
private let registerForRemoteNotifications: () -> AnyPublisher<String, Error>
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(appDelegate: AppDelegate,
|
||||
allIdentitiesService: AllIdentitiesService,
|
||||
userNotificationService: UserNotificationService) {
|
||||
self.appDelegate = appDelegate
|
||||
self.allIdentitiesService = allIdentitiesService
|
||||
self.userNotificationService = userNotificationService
|
||||
public init(environment: AppEnvironment,
|
||||
registerForRemoteNotifications: @escaping () -> AnyPublisher<String, Error>) throws {
|
||||
allIdentitiesService = try AllIdentitiesService(environment: environment)
|
||||
userNotificationService = UserNotificationService(environment: environment)
|
||||
self.registerForRemoteNotifications = registerForRemoteNotifications
|
||||
|
||||
allIdentitiesService.mostRecentlyUsedIdentityID.assign(to: &$mostRecentlyUsedIdentityID)
|
||||
|
||||
|
@ -28,7 +25,7 @@ class RootViewModel: ObservableObject {
|
|||
|
||||
userNotificationService.isAuthorized()
|
||||
.filter { $0 }
|
||||
.zip(appDelegate.registerForRemoteNotifications())
|
||||
.zip(registerForRemoteNotifications())
|
||||
.map { $1 }
|
||||
.flatMap(allIdentitiesService.updatePushSubscriptions(deviceToken:))
|
||||
.sink { _ in } receiveValue: { _ in }
|
||||
|
@ -36,7 +33,7 @@ class RootViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension RootViewModel {
|
||||
public extension RootViewModel {
|
||||
func newIdentitySelected(id: UUID?) {
|
||||
guard let id = id else {
|
||||
tabNavigationViewModel = nil
|
||||
|
@ -64,7 +61,7 @@ extension RootViewModel {
|
|||
|
||||
userNotificationService.isAuthorized()
|
||||
.filter { $0 }
|
||||
.zip(appDelegate.registerForRemoteNotifications())
|
||||
.zip(registerForRemoteNotifications())
|
||||
.filter { identityService.identity.lastRegisteredDeviceToken != $1 }
|
||||
.map { ($1, identityService.identity.pushSubscriptionAlerts) }
|
||||
.flatMap(identityService.createPushSubscription(deviceToken:alerts:))
|
|
@ -3,8 +3,9 @@
|
|||
import Foundation
|
||||
import ServiceLayer
|
||||
|
||||
class SecondaryNavigationViewModel: ObservableObject {
|
||||
@Published private(set) var identity: Identity
|
||||
public class SecondaryNavigationViewModel: ObservableObject {
|
||||
@Published public private(set) var identity: Identity
|
||||
|
||||
private let identityService: IdentityService
|
||||
|
||||
init(identityService: IdentityService) {
|
||||
|
@ -14,7 +15,7 @@ class SecondaryNavigationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension SecondaryNavigationViewModel {
|
||||
public extension SecondaryNavigationViewModel {
|
||||
func identitiesViewModel() -> IdentitiesViewModel {
|
||||
IdentitiesViewModel(identityService: identityService)
|
||||
}
|
|
@ -5,11 +5,11 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
class StatusListViewModel: ObservableObject {
|
||||
@Published private(set) var statusIDs = [[String]]()
|
||||
@Published var alertItem: AlertItem?
|
||||
@Published private(set) var loading = false
|
||||
private(set) var maintainScrollPositionOfStatusID: String?
|
||||
public class StatusListViewModel: ObservableObject {
|
||||
@Published public private(set) var statusIDs = [[String]]()
|
||||
@Published public var alertItem: AlertItem?
|
||||
@Published public private(set) var loading = false
|
||||
public private(set) var maintainScrollPositionOfStatusID: String?
|
||||
|
||||
private var statuses = [String: Status]()
|
||||
private let statusListService: StatusListService
|
||||
|
@ -27,19 +27,21 @@ class StatusListViewModel: ObservableObject {
|
|||
self?.cleanViewModelCache(newStatusSections: $0)
|
||||
self?.statuses = Dictionary(uniqueKeysWithValues: $0.reduce([], +).map { ($0.id, $0) })
|
||||
})
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.map { $0.map { $0.map(\.id) } }
|
||||
.assign(to: &$statusIDs)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusListViewModel {
|
||||
public extension StatusListViewModel {
|
||||
var paginates: Bool { statusListService.paginates }
|
||||
|
||||
var contextParentID: String? { statusListService.contextParentID }
|
||||
|
||||
func request(maxID: String? = nil, minID: String? = nil) {
|
||||
statusListService.request(maxID: maxID, minID: minID)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
.handleEvents(
|
||||
receiveSubscription: { [weak self] _ in self?.loading = true },
|
|
@ -5,24 +5,24 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
struct StatusViewModel {
|
||||
let content: NSAttributedString
|
||||
let contentEmoji: [Emoji]
|
||||
let displayName: String
|
||||
let displayNameEmoji: [Emoji]
|
||||
let spoilerText: String
|
||||
let isReblog: Bool
|
||||
let rebloggedByDisplayName: String
|
||||
let rebloggedByDisplayNameEmoji: [Emoji]
|
||||
let attachmentViewModels: [AttachmentViewModel]
|
||||
let pollOptionTitles: [String]
|
||||
let pollEmoji: [Emoji]
|
||||
var isPinned = false
|
||||
var isContextParent = false
|
||||
var isReplyInContext = false
|
||||
var hasReplyFollowing = false
|
||||
var sensitiveContentToggled = false
|
||||
let events: AnyPublisher<AnyPublisher<Never, Error>, Never>
|
||||
public struct StatusViewModel {
|
||||
public let content: NSAttributedString
|
||||
public let contentEmoji: [Emoji]
|
||||
public let displayName: String
|
||||
public let displayNameEmoji: [Emoji]
|
||||
public let spoilerText: String
|
||||
public let isReblog: Bool
|
||||
public let rebloggedByDisplayName: String
|
||||
public let rebloggedByDisplayNameEmoji: [Emoji]
|
||||
public let attachmentViewModels: [AttachmentViewModel]
|
||||
public let pollOptionTitles: [String]
|
||||
public let pollEmoji: [Emoji]
|
||||
public var isPinned = false
|
||||
public var isContextParent = false
|
||||
public var isReplyInContext = false
|
||||
public var hasReplyFollowing = false
|
||||
public var sensitiveContentToggled = false
|
||||
public let events: AnyPublisher<AnyPublisher<Never, Error>, Never>
|
||||
|
||||
private let statusService: StatusService
|
||||
private let eventsInput = PassthroughSubject<AnyPublisher<Never, Error>, Never>()
|
||||
|
@ -49,7 +49,7 @@ struct StatusViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
extension StatusViewModel {
|
||||
public extension StatusViewModel {
|
||||
var shouldDisplaySensitiveContent: Bool {
|
||||
if statusService.status.displayStatus.sensitive {
|
||||
return sensitiveContentToggled
|
|
@ -5,14 +5,14 @@ import Combine
|
|||
import Mastodon
|
||||
import ServiceLayer
|
||||
|
||||
class TabNavigationViewModel: ObservableObject {
|
||||
@Published private(set) var identity: Identity
|
||||
@Published private(set) var recentIdentities = [Identity]()
|
||||
@Published var timeline = Timeline.home
|
||||
@Published private(set) var timelinesAndLists = Timeline.nonLists
|
||||
@Published var presentingSecondaryNavigation = false
|
||||
@Published var alertItem: AlertItem?
|
||||
var selectedTab: Tab? = .timelines
|
||||
public class TabNavigationViewModel: ObservableObject {
|
||||
@Published public private(set) var identity: Identity
|
||||
@Published public private(set) var recentIdentities = [Identity]()
|
||||
@Published public var timeline = Timeline.home
|
||||
@Published public private(set) var timelinesAndLists = Timeline.nonLists
|
||||
@Published public var presentingSecondaryNavigation = false
|
||||
@Published public var alertItem: AlertItem?
|
||||
public var selectedTab: Tab? = .timelines
|
||||
|
||||
private let identityService: IdentityService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
@ -33,7 +33,7 @@ class TabNavigationViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
extension TabNavigationViewModel {
|
||||
public extension TabNavigationViewModel {
|
||||
var timelineSubtitle: String {
|
||||
switch timeline {
|
||||
case .home, .list:
|
||||
|
@ -93,7 +93,7 @@ extension TabNavigationViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
extension TabNavigationViewModel {
|
||||
public extension TabNavigationViewModel {
|
||||
enum Tab: CaseIterable {
|
||||
case timelines
|
||||
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 {
|
||||
var id: Self { self }
|
||||
public var id: Self { self }
|
||||
}
|
|
@ -5,13 +5,13 @@ import Combine
|
|||
import CombineExpectations
|
||||
import HTTP
|
||||
import Mastodon
|
||||
@testable import Metatext
|
||||
|
||||
import ServiceLayer
|
||||
import ServiceLayerMocks
|
||||
@testable import ViewModels
|
||||
|
||||
class AddIdentityViewModelTests: XCTestCase {
|
||||
func testAddIdentity() throws {
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||
let addedIDRecorder = sut.addedIdentityID.record()
|
||||
|
||||
sut.urlFieldText = "https://mastodon.social"
|
||||
|
@ -21,7 +21,7 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testAddIdentityWithoutScheme() throws {
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||
let addedIDRecorder = sut.addedIdentityID.record()
|
||||
|
||||
sut.urlFieldText = "mastodon.social"
|
||||
|
@ -31,7 +31,7 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testInvalidURL() throws {
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: try AllIdentitiesService(environment: .mock()))
|
||||
let recorder = sut.$alertItem.record()
|
||||
|
||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
||||
|
@ -50,7 +50,9 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
webAuthSessionType: CanceledLoginMockWebAuthSession.self,
|
||||
keychainServiceType: MockKeychainService.self,
|
||||
userDefaults: MockUserDefaults(),
|
||||
inMemoryContent: true)
|
||||
userNotificationClient: .mock,
|
||||
inMemoryContent: true,
|
||||
identityFixture: nil)
|
||||
let allIdentitiesService = try AllIdentitiesService(environment: environment)
|
||||
let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService)
|
||||
let recorder = sut.$alertItem.record()
|
||||
|
@ -62,8 +64,4 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
|
||||
try wait(for: recorder.next().inverted, timeout: 1)
|
||||
}
|
||||
|
||||
func testFuck() {
|
||||
|
||||
}
|
||||
}
|
|
@ -4,15 +4,16 @@ import XCTest
|
|||
import Combine
|
||||
import CombineExpectations
|
||||
import ServiceLayer
|
||||
@testable import Metatext
|
||||
import ServiceLayerMocks
|
||||
@testable import ViewModels
|
||||
|
||||
class RootViewModelTests: XCTestCase {
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
func testAddIdentity() throws {
|
||||
let sut = RootViewModel(appDelegate: AppDelegate(),
|
||||
allIdentitiesService: .fresh,
|
||||
userNotificationService: UserNotificationService())
|
||||
let sut = try RootViewModel(
|
||||
environment: .mock(),
|
||||
registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() })
|
||||
let recorder = sut.$tabNavigationViewModel.record()
|
||||
|
||||
XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct AddIdentityView: View {
|
||||
@StateObject var viewModel: AddIdentityViewModel
|
||||
|
@ -40,9 +41,11 @@ extension AddIdentityView {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct AddAccountView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddIdentityView(viewModel: .development)
|
||||
AddIdentityView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
import ViewModels
|
||||
|
||||
class AttachmentView: UIView {
|
||||
let imageView = AnimatedImageView()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
class AttachmentsView: UIView {
|
||||
private let containerStackView = UIStackView()
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import struct Mastodon.Filter
|
||||
import ViewModels
|
||||
|
||||
struct EditFilterView: View {
|
||||
@StateObject var viewModel: EditFilterViewModel
|
||||
|
@ -96,9 +97,11 @@ extension Filter.Context {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct EditFilterView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EditFilterView(viewModel: .development)
|
||||
EditFilterView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import struct Mastodon.Filter
|
||||
import ViewModels
|
||||
|
||||
struct FiltersView: View {
|
||||
@StateObject var viewModel: FiltersViewModel
|
||||
|
@ -55,9 +56,11 @@ private extension FiltersView {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct FiltersView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FiltersView(viewModel: .development)
|
||||
FiltersView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import KingfisherSwiftUI
|
||||
import struct ServiceLayer.Identity
|
||||
import ViewModels
|
||||
|
||||
struct IdentitiesView: View {
|
||||
@StateObject var viewModel: IdentitiesViewModel
|
||||
|
@ -68,10 +68,12 @@ struct IdentitiesView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct IdentitiesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
IdentitiesView(viewModel: .development)
|
||||
.environmentObject(RootViewModel.development)
|
||||
IdentitiesView(viewModel: .mock())
|
||||
.environmentObject(RootViewModel.mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct ListsView: View {
|
||||
@StateObject var viewModel: ListsViewModel
|
||||
|
@ -55,10 +56,12 @@ struct ListsView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct ListsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ListsView(viewModel: .development)
|
||||
.environmentObject(TabNavigationViewModel.development)
|
||||
ListsView(viewModel: .mock())
|
||||
.environmentObject(TabNavigationViewModel.mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct NotificationTypesPreferencesView: View {
|
||||
@StateObject var viewModel: NotificationTypesPreferencesViewModel
|
||||
|
@ -24,9 +25,11 @@ struct NotificationTypesPreferencesView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct NotificationTypesPreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NotificationTypesPreferencesView(viewModel: .development)
|
||||
NotificationTypesPreferencesView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import SwiftUI
|
||||
import class Mastodon.Status
|
||||
import struct Mastodon.Preferences
|
||||
import ViewModels
|
||||
|
||||
struct PostingReadingPreferencesView: View {
|
||||
@StateObject var viewModel: PostingReadingPreferencesViewModel
|
||||
|
@ -50,9 +51,11 @@ struct PostingReadingPreferencesView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct PostingReadingPreferencesViewView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PostingReadingPreferencesView(viewModel: .development)
|
||||
PostingReadingPreferencesView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct PreferencesView: View {
|
||||
@StateObject var viewModel: PreferencesViewModel
|
||||
|
@ -26,9 +27,11 @@ struct PreferencesView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct PreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PreferencesView(viewModel: .development)
|
||||
PreferencesView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct RootView: View {
|
||||
@StateObject var viewModel: RootViewModel
|
||||
|
@ -20,9 +21,11 @@ struct RootView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RootView(viewModel: .development)
|
||||
RootView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import KingfisherSwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct SecondaryNavigationView: View {
|
||||
@StateObject var viewModel: SecondaryNavigationViewModel
|
||||
|
@ -66,11 +67,13 @@ struct SecondaryNavigationView: View {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct SecondaryNavigationView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SecondaryNavigationView(viewModel: .development)
|
||||
.environmentObject(RootViewModel.development)
|
||||
.environmentObject(TabNavigationViewModel.development)
|
||||
SecondaryNavigationView(viewModel: .mock())
|
||||
.environmentObject(RootViewModel.mock())
|
||||
.environmentObject(TabNavigationViewModel.mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import AVKit
|
||||
import Kingfisher
|
||||
import UIKit
|
||||
import ViewModels
|
||||
|
||||
protocol StatusTableViewCellDelegate: class {
|
||||
func statusTableViewCellDidHaveShareButtonTapped(_ cell: StatusTableViewCell)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct StatusListView: UIViewControllerRepresentable {
|
||||
let viewModel: StatusListViewModel
|
||||
|
@ -15,9 +16,11 @@ struct StatusListView: UIViewControllerRepresentable {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
import PreviewViewModels
|
||||
|
||||
struct StatusListView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
StatusListView(viewModel: .development)
|
||||
StatusListView(viewModel: .mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import KingfisherSwiftUI
|
||||
import ViewModels
|
||||
|
||||
struct TabNavigationView: View {
|
||||
@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
|
||||
import PreviewViewModels
|
||||
|
||||
struct TabNavigation_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TabNavigationView(viewModel: .development)
|
||||
.environmentObject(RootViewModel.development)
|
||||
TabNavigationView(viewModel: .mock())
|
||||
.environmentObject(RootViewModel.mock())
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue