From caf32a661bab5f68a2b9b11e088b356815a49655 Mon Sep 17 00:00:00 2001 From: Maxime Marinel Date: Mon, 29 Jan 2024 22:29:25 +0100 Subject: [PATCH] Adopt concurrency --- App/Features/Entry/Add/AddEntryModel.swift | 30 +++++++++++ App/Features/Entry/Add/AddEntryView.swift | 34 ++++++++++++ App/Features/Entry/AddEntryView.swift | 53 ------------------- App/Features/Entry/EntriesListView.swift | 2 +- App/Features/Entry/EntriesView.swift | 2 +- App/Features/Entry/EntryView.swift | 2 +- App/Features/MainView.swift | 3 +- .../ClientIdClientSecretView.swift | 24 +++++---- .../ClientIdClientSecretViewModel.swift | 31 +++++------ .../Registration/Login/LoginView.swift | 2 +- .../Registration/Login/LoginViewModel.swift | 27 +++++----- .../Registration/RegistrationView.swift | 10 ++-- .../Registration/Server/ServerView.swift | 12 +++-- .../Registration/Server/ServerViewModel.swift | 25 +++++---- App/Features/Router/Router.swift | 7 +-- App/Features/Sync/AppSync.swift | 14 +++-- App/Features/Sync/RefreshButton.swift | 4 +- App/Features/Tip/TipView.swift | 10 ++-- App/Features/Tip/TipViewModel.swift | 12 +++-- .../WallabagPlusProtectedModifier.swift | 2 +- .../WallabagPlus/WallabagPlusStore.swift | 13 ++--- .../WallabagPlusSubscribeView.swift | 2 +- App/Lib/WallabagSession.swift | 4 +- App/WallabagApp.swift | 13 ++--- wallabag.xcodeproj/project.pbxproj | 14 ++++- 25 files changed, 189 insertions(+), 163 deletions(-) create mode 100644 App/Features/Entry/Add/AddEntryModel.swift create mode 100644 App/Features/Entry/Add/AddEntryView.swift delete mode 100644 App/Features/Entry/AddEntryView.swift diff --git a/App/Features/Entry/Add/AddEntryModel.swift b/App/Features/Entry/Add/AddEntryModel.swift new file mode 100644 index 00000000..a772205e --- /dev/null +++ b/App/Features/Entry/Add/AddEntryModel.swift @@ -0,0 +1,30 @@ +import Factory +import Foundation +import Observation +import SwiftUI + +@Observable +final class AddEntryModel { + @ObservationIgnored + @Injected(\.wallabagSession) private var session + + var url: String = "" + var submitting: Bool = false + var succeeded: Bool = false + + @MainActor + func addEntry() async { + defer { + submitting = false + succeeded = false + url = "" + } + + submitting = true + do { + try await session.addEntry(url: url) + succeeded = true + try await Task.sleep(for: .seconds(3)) + } catch {} + } +} diff --git a/App/Features/Entry/Add/AddEntryView.swift b/App/Features/Entry/Add/AddEntryView.swift new file mode 100644 index 00000000..ce647aca --- /dev/null +++ b/App/Features/Entry/Add/AddEntryView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +struct AddEntryView: View { + @State private var model = AddEntryModel() + + var body: some View { + Form { + TextField("Url", text: $model.url) + #if os(iOS) + .autocapitalization(.none) + #endif + .disableAutocorrection(true) + HStack { + if model.succeeded { + Text("Great! Entry was added") + } else { + Button(model.submitting ? "Submitting..." : "Submit") { + Task { + await model.addEntry() + } + }.disabled(model.submitting) + } + } + .disabled(model.url.isEmpty) + } + .animation(.easeInOut, value: model.succeeded) + .animation(.easeInOut, value: model.submitting) + .navigationTitle("Add entry") + } +} + +#Preview { + AddEntryView() +} diff --git a/App/Features/Entry/AddEntryView.swift b/App/Features/Entry/AddEntryView.swift deleted file mode 100644 index b118af01..00000000 --- a/App/Features/Entry/AddEntryView.swift +++ /dev/null @@ -1,53 +0,0 @@ -import Factory -import SwiftUI - -struct AddEntryView: View { - @StateObject private var model = AddEntryModel() - - var body: some View { - Form { - TextField("Url", text: $model.url) - #if os(iOS) - .autocapitalization(.none) - #endif - .disableAutocorrection(true) - HStack { - Button(model.submitting ? "Submitting..." : "Submit") { - Task { - await model.addEntry() - } - }.disabled(model.submitting) - }.disabled(model.url.isEmpty) - if model.succeeded { - Text("Great! Entry was added") - } - } - .navigationTitle("Add entry") - } -} - -private class AddEntryModel: ObservableObject { - @Injected(\.wallabagSession) private var session - - @Published var url: String = "" - @Published var submitting: Bool = false - @Published var succeeded: Bool = false - - @MainActor - func addEntry() async { - submitting = true - try? await session.addEntry(url: url) - url = "" - submitting = false - withAnimation { - self.succeeded = true - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { - self.succeeded = false - } - } - } -} - -#Preview { - AddEntryView() -} diff --git a/App/Features/Entry/EntriesListView.swift b/App/Features/Entry/EntriesListView.swift index 2574435a..a0f3f1d7 100644 --- a/App/Features/Entry/EntriesListView.swift +++ b/App/Features/Entry/EntriesListView.swift @@ -4,7 +4,7 @@ import SwiftUI struct EntriesListView: View { @Environment(\.managedObjectContext) var context: NSManagedObjectContext - @EnvironmentObject var appSync: AppSync + @Environment(AppSync.self) var appSync: AppSync @FetchRequest var entries: FetchedResults init(predicate: NSPredicate, entriesSortAscending: Bool) { diff --git a/App/Features/Entry/EntriesView.swift b/App/Features/Entry/EntriesView.swift index 41268ed1..f8f1e903 100644 --- a/App/Features/Entry/EntriesView.swift +++ b/App/Features/Entry/EntriesView.swift @@ -3,7 +3,7 @@ import CoreData import SwiftUI struct EntriesView: View { - @EnvironmentObject var router: Router + @Environment(Router.self) var router: Router @EnvironmentObject var appState: AppState @StateObject var searchViewModel = SearchViewModel() @State var entriesSortAscending = false diff --git a/App/Features/Entry/EntryView.swift b/App/Features/Entry/EntryView.swift index 4b5aaa6a..8bc8c55e 100644 --- a/App/Features/Entry/EntryView.swift +++ b/App/Features/Entry/EntryView.swift @@ -7,7 +7,7 @@ struct EntryView: View { @Environment(\.managedObjectContext) var context: NSManagedObjectContext @Environment(\.openURL) var openURL @Environment(\.dismiss) private var dismiss - @EnvironmentObject var appSync: AppSync + @Environment(AppSync.self) var appSync: AppSync #if os(iOS) @EnvironmentObject var player: PlayerPublisher #endif diff --git a/App/Features/MainView.swift b/App/Features/MainView.swift index 31edb693..cc1f9378 100644 --- a/App/Features/MainView.swift +++ b/App/Features/MainView.swift @@ -4,7 +4,7 @@ import SwiftUI struct MainView: View { @EnvironmentObject var appState: AppState @EnvironmentObject var player: PlayerPublisher - @EnvironmentObject var router: Router + @Environment(Router.self) var router: Router var body: some View { if appState.registred { @@ -18,6 +18,7 @@ struct MainView: View { var mainView: some View { ZStack { GeometryReader { geometry in + @Bindable var router = router NavigationStack(path: $router.path) { EntriesView() .appRouting() diff --git a/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretView.swift b/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretView.swift index cafcb5f5..7cde36f7 100644 --- a/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretView.swift +++ b/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretView.swift @@ -2,7 +2,7 @@ import SharedLib import SwiftUI struct ClientIdClientSecretView: View { - @StateObject var clientIdSecretViewModel = ClientIdSecretViewModel() + @State private var clientIdSecretViewModel = ClientIdSecretViewModel() var body: some View { Form { @@ -20,7 +20,15 @@ struct ClientIdClientSecretView: View { .autocapitalization(.none) #endif } - NavigationLink("Next", destination: LoginView()).disabled(!clientIdSecretViewModel.isValid) + Button(action: { + clientIdSecretViewModel.goNext() + }, label: { + Text("Next") + }) + .disabled(!clientIdSecretViewModel.isValid) + } + .navigationDestination(isPresented: $clientIdSecretViewModel.shouldGoNextStep) { + LoginView() } #if os(iOS) .navigationBarTitle("Client id & secret") @@ -32,12 +40,8 @@ struct ClientIdClientSecretView: View { } } -#if DEBUG - struct ClientIdClientSecretView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - ClientIdClientSecretView() - } - } +#Preview { + NavigationView { + ClientIdClientSecretView() } -#endif +} diff --git a/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretViewModel.swift b/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretViewModel.swift index b01d968b..ca936c40 100644 --- a/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretViewModel.swift +++ b/App/Features/Registration/ClientIdClientSecret/ClientIdClientSecretViewModel.swift @@ -1,30 +1,25 @@ -import Combine import Foundation +import Observation import SharedLib -import SwiftUI -class ClientIdSecretViewModel: ObservableObject { - private(set) var isValid: Bool = false - - @Published var clientId: String = "" - @Published var clientSecret: String = "" +@Observable +final class ClientIdSecretViewModel { + var isValid: Bool { + !clientId.isEmpty && !clientSecret.isEmpty + } - private var cancellable: AnyCancellable? + var clientId: String = "" + var clientSecret: String = "" + var shouldGoNextStep = false init() { clientId = WallabagUserDefaults.clientId clientSecret = WallabagUserDefaults.clientSecret - - cancellable = Publishers.CombineLatest($clientId, $clientSecret).sink { [unowned self] clientId, clientSecret in - isValid = !clientId.isEmpty && !clientSecret.isEmpty - if isValid { - WallabagUserDefaults.clientId = clientId.trimmingCharacters(in: .whitespacesAndNewlines) - WallabagUserDefaults.clientSecret = clientSecret.trimmingCharacters(in: .whitespacesAndNewlines) - } - } } - deinit { - cancellable?.cancel() + func goNext() { + WallabagUserDefaults.clientId = clientId.trimmingCharacters(in: .whitespacesAndNewlines) + WallabagUserDefaults.clientSecret = clientSecret.trimmingCharacters(in: .whitespacesAndNewlines) + shouldGoNextStep = true } } diff --git a/App/Features/Registration/Login/LoginView.swift b/App/Features/Registration/Login/LoginView.swift index 0e97431d..859c259a 100644 --- a/App/Features/Registration/Login/LoginView.swift +++ b/App/Features/Registration/Login/LoginView.swift @@ -1,7 +1,7 @@ import SwiftUI struct LoginView: View { - @StateObject var loginViewModel = LoginViewModel() + @State var loginViewModel = LoginViewModel() var body: some View { Form { diff --git a/App/Features/Registration/Login/LoginViewModel.swift b/App/Features/Registration/Login/LoginViewModel.swift index 96de93ac..7bab5bf4 100644 --- a/App/Features/Registration/Login/LoginViewModel.swift +++ b/App/Features/Registration/Login/LoginViewModel.swift @@ -1,25 +1,28 @@ import Combine import Factory import Foundation +import Observation import SharedLib -class LoginViewModel: ObservableObject { +@Observable +final class LoginViewModel { + @ObservationIgnored @Injected(\.appState) private var appState + @ObservationIgnored @Injected(\.wallabagSession) private var session + @ObservationIgnored @Injected(\.router) private var router - - @Published var login: String = "" - @Published var password: String = "" - @Published var error: String? - - private(set) var isValid: Bool = false private var cancellable = Set() + var login: String = "" + var password: String = "" + var error: String? + var isValid: Bool { + !login.isEmpty && !password.isEmpty + } + init() { login = WallabagUserDefaults.login - Publishers.CombineLatest($login, $password).sink { [unowned self] login, password in - isValid = !login.isEmpty && !password.isEmpty - }.store(in: &cancellable) session.$state.receive(on: DispatchQueue.main).sink { [unowned self] state in switch state { @@ -45,8 +48,4 @@ class LoginViewModel: ObservableObject { password: password ) } - - deinit { - cancellable.forEach { $0.cancel() } - } } diff --git a/App/Features/Registration/RegistrationView.swift b/App/Features/Registration/RegistrationView.swift index deca59ce..9dffc38c 100644 --- a/App/Features/Registration/RegistrationView.swift +++ b/App/Features/Registration/RegistrationView.swift @@ -19,10 +19,6 @@ struct RegistrationView: View { } } -#if DEBUG - struct RegistrationView_Previews: PreviewProvider { - static var previews: some View { - RegistrationView() - } - } -#endif +#Preview { + RegistrationView() +} diff --git a/App/Features/Registration/Server/ServerView.swift b/App/Features/Registration/Server/ServerView.swift index 11c242ca..70fa073b 100644 --- a/App/Features/Registration/Server/ServerView.swift +++ b/App/Features/Registration/Server/ServerView.swift @@ -1,7 +1,7 @@ import SwiftUI struct ServerView: View { - @StateObject var serverViewModel = ServerViewModel() + @State private var serverViewModel = ServerViewModel() var body: some View { Form { @@ -12,9 +12,15 @@ struct ServerView: View { .autocapitalization(.none) #endif } - NavigationLink(destination: ClientIdClientSecretView()) { + Button(action: { + serverViewModel.goNext() + }, label: { Text("Next") - }.disabled(!serverViewModel.isValid) + }) + .disabled(!serverViewModel.isValid) + } + .navigationDestination(isPresented: $serverViewModel.shouldGoNextStep) { + ClientIdClientSecretView() } #if os(iOS) .navigationBarTitle("Server") diff --git a/App/Features/Registration/Server/ServerViewModel.swift b/App/Features/Registration/Server/ServerViewModel.swift index b0ae09c1..6510d202 100644 --- a/App/Features/Registration/Server/ServerViewModel.swift +++ b/App/Features/Registration/Server/ServerViewModel.swift @@ -1,26 +1,25 @@ -import Combine import Foundation +import Observation import SharedLib import SwiftUI -final class ServerViewModel: ObservableObject { - @Published private(set) var isValid: Bool = false - @Published var url: String = "" +@Observable +final class ServerViewModel { + var isValid: Bool { + validateServer(host: url) + } + + var url: String = "" - private var cancellable: AnyCancellable? + var shouldGoNextStep = false init() { url = WallabagUserDefaults.host - cancellable = $url.sink { [unowned self] url in - isValid = validateServer(host: url) - if isValid { - WallabagUserDefaults.host = url - } - } } - deinit { - cancellable?.cancel() + func goNext() { + WallabagUserDefaults.host = url + shouldGoNextStep = true } private func validateServer(host: String) -> Bool { diff --git a/App/Features/Router/Router.swift b/App/Features/Router/Router.swift index 05923d0b..ad7c560c 100644 --- a/App/Features/Router/Router.swift +++ b/App/Features/Router/Router.swift @@ -1,7 +1,8 @@ -import Combine import Foundation +import Observation import SwiftUI -final class Router: ObservableObject { - @Published var path: [RoutePath] = [] +@Observable +final class Router { + var path: [RoutePath] = [] } diff --git a/App/Features/Sync/AppSync.swift b/App/Features/Sync/AppSync.swift index aa2645b2..97139651 100644 --- a/App/Features/Sync/AppSync.swift +++ b/App/Features/Sync/AppSync.swift @@ -1,19 +1,25 @@ -import Combine import CoreData import Factory import Foundation +import Observation import SharedLib import WallabagKit -final class AppSync: ObservableObject { +@Observable +final class AppSync { + @ObservationIgnored @Injected(\.wallabagSession) private var session + @ObservationIgnored @Injected(\.errorHandler) private var errorViewModel + @ObservationIgnored @Injected(\.coreData) private var coreData + @ObservationIgnored @CoreDataViewContext var coreDataContext: NSManagedObjectContext - @Published private(set) var inProgress = false - @Published private(set) var progress: Float = 0.0 + private(set) var inProgress = false + private(set) var progress: Float = 0.0 + @ObservationIgnored private lazy var backgroundContext: NSManagedObjectContext = { let context = coreData.persistentContainer.newBackgroundContext() context.mergePolicy = NSOverwriteMergePolicy diff --git a/App/Features/Sync/RefreshButton.swift b/App/Features/Sync/RefreshButton.swift index ebbe3c76..036bc2c0 100644 --- a/App/Features/Sync/RefreshButton.swift +++ b/App/Features/Sync/RefreshButton.swift @@ -1,7 +1,7 @@ import SwiftUI struct RefreshButton: View { - @EnvironmentObject var appSync: AppSync + @Environment(AppSync.self) var appSync: AppSync var body: some View { HStack { @@ -31,6 +31,6 @@ struct RefreshButton: View { struct RefreshButton_Previews: PreviewProvider { static var previews: some View { - RefreshButton().environmentObject(AppSync()) + RefreshButton().environment(AppSync()) } } diff --git a/App/Features/Tip/TipView.swift b/App/Features/Tip/TipView.swift index e27d9324..2e1c8f20 100644 --- a/App/Features/Tip/TipView.swift +++ b/App/Features/Tip/TipView.swift @@ -1,7 +1,7 @@ import SwiftUI struct TipView: View { - @StateObject var tipViewModel = TipViewModel() + @State var tipViewModel = TipViewModel() var body: some View { VStack(alignment: .leading) { @@ -48,13 +48,11 @@ struct TipView: View { } @MainActor - func purchase() async { + private func purchase() async { _ = try? await tipViewModel.purchaseTip() } } -struct TipView_Previews: PreviewProvider { - static var previews: some View { - TipView() - } +#Preview { + TipView() } diff --git a/App/Features/Tip/TipViewModel.swift b/App/Features/Tip/TipViewModel.swift index 9af125cb..2b48e4cb 100644 --- a/App/Features/Tip/TipViewModel.swift +++ b/App/Features/Tip/TipViewModel.swift @@ -1,12 +1,14 @@ import Foundation +import Observation import StoreKit -final class TipViewModel: ObservableObject { - @Published var canMakePayments: Bool = false - @Published var tipProduct: Product? - @Published var paymentSuccess = false +@Observable +final class TipViewModel { + var canMakePayments: Bool = false + var tipProduct: Product? + var paymentSuccess = false - var taskHandle: Task? + private var taskHandle: Task? init() { canMakePayments = SKPaymentQueue.canMakePayments() diff --git a/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift b/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift index 8452b3f4..8e7b8bcb 100644 --- a/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift +++ b/App/Features/WallabagPlus/WallabagPlusProtectedModifier.swift @@ -1,7 +1,7 @@ import SwiftUI struct WallabagPlusProtectedModifier: ViewModifier { - @EnvironmentObject private var wallabagPlusStore: WallabagPlusStore + @Environment(WallabagPlusStore.self) private var wallabagPlusStore func body(content: Content) -> some View { VStack { diff --git a/App/Features/WallabagPlus/WallabagPlusStore.swift b/App/Features/WallabagPlus/WallabagPlusStore.swift index fe8ed77a..56839d6b 100644 --- a/App/Features/WallabagPlus/WallabagPlusStore.swift +++ b/App/Features/WallabagPlus/WallabagPlusStore.swift @@ -1,19 +1,14 @@ -// -// WallabagPlusStore.swift -// wallabag -// -// Created by maxime marinel on 11/01/2024. -// - import Foundation +import Observation import StoreKit -final class WallabagPlusStore: ObservableObject { +@Observable +final class WallabagPlusStore { private var obs: Task? let groupID = "21433277" - @Published var proUnlocked = false + var proUnlocked = false init() { obs = observeTransactionUpdates() diff --git a/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift b/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift index 21940587..ade5e7de 100644 --- a/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift +++ b/App/Features/WallabagPlus/WallabagPlusSubscribeView.swift @@ -15,7 +15,7 @@ struct WallabagPlusSubscribeView: View { @BundleKey("TERMS_OF_USE_URL") private var termsOfUseURL - @EnvironmentObject var wallabagPlusStore: WallabagPlusStore + @Environment(WallabagPlusStore.self) var wallabagPlusStore var body: some View { SubscriptionStoreView(groupID: wallabagPlusStore.groupID, visibleRelationships: .all) diff --git a/App/Lib/WallabagSession.swift b/App/Lib/WallabagSession.swift index 5ee34634..df57e8ad 100644 --- a/App/Lib/WallabagSession.swift +++ b/App/Lib/WallabagSession.swift @@ -5,7 +5,7 @@ import Foundation import SharedLib import WallabagKit -class WallabagSession: ObservableObject { +final class WallabagSession: ObservableObject { enum State { case unknown case connecting @@ -43,7 +43,7 @@ class WallabagSession: ObservableObject { case WallabagKitError.invalidApiEndpoint: state = .error(reason: "Invalid api endpoint, check your host configuration") default: - state = .error(reason: "Unknown error") + state = .error(reason: error.localizedDescription) } } } diff --git a/App/WallabagApp.swift b/App/WallabagApp.swift index d1a18557..319d84a7 100644 --- a/App/WallabagApp.swift +++ b/App/WallabagApp.swift @@ -13,14 +13,15 @@ struct WallabagApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate #endif - @InjectedObject(\.wallabagPlusStore) private var wallabagPlusStore + @State private var router = Container.shared.router() + @State private var appSync = Container.shared.appSync() + @State private var wallabagPlusStore = Container.shared.wallabagPlusStore() + @InjectedObject(\.appState) private var appState - @InjectedObject(\.router) private var router #if os(iOS) @InjectedObject(\.playerPublisher) private var playerPublisher #endif @InjectedObject(\.errorHandler) private var errorHandler - @InjectedObject(\.appSync) private var appSync @InjectedObject(\.appSetting) private var appSetting @Injected(\.coreDataSync) private var coreDataSync @Injected(\.coreData) private var coreData @@ -32,11 +33,11 @@ struct WallabagApp: App { #if os(iOS) .environmentObject(playerPublisher) #endif - .environmentObject(router) + .environment(router) + .environment(appSync) + .environment(wallabagPlusStore) .environmentObject(errorHandler) - .environmentObject(appSync) .environmentObject(appSetting) - .environmentObject(wallabagPlusStore) .environment(\.managedObjectContext, coreData.viewContext) .subscriptionStatusTask(for: wallabagPlusStore.groupID) { task in _ = await task.map { statues in diff --git a/wallabag.xcodeproj/project.pbxproj b/wallabag.xcodeproj/project.pbxproj index 3b3cc29f..0b52c21e 100644 --- a/wallabag.xcodeproj/project.pbxproj +++ b/wallabag.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 09190AFB2ACD461B00634123 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0951C61729CC2EB000D8E8C6 /* Assets.xcassets */; }; + 0940869A2B684842000DA980 /* AddEntryModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094086992B684842000DA980 /* AddEntryModel.swift */; }; 094AA03E2629EB1F006E5605 /* wallabagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094AA03D2629EB1F006E5605 /* wallabagTests.swift */; }; 094AA05A2629EB60006E5605 /* ImageCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094A9FED2629E6BB006E5605 /* ImageCacheTests.swift */; }; 094AA0692629EB98006E5605 /* ImageDownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094AA00F2629E739006E5605 /* ImageDownloaderTests.swift */; }; @@ -146,6 +147,7 @@ /* Begin PBXFileReference section */ 093D137226263A96008C2150 /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = Library/Frameworks/IntentsUI.framework; sourceTree = DEVELOPER_DIR; }; 093D139E26263B05008C2150 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; + 094086992B684842000DA980 /* AddEntryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEntryModel.swift; sourceTree = ""; }; 09483CC6270498C20084CEB2 /* SharedLib.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SharedLib.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 09483CCC270499120084CEB2 /* WallabagKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WallabagKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 09483CCF27049B3E0084CEB2 /* WallabagKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WallabagKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -299,6 +301,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 094086972B684837000DA980 /* Add */ = { + isa = PBXGroup; + children = ( + 09644C5725C98596000FFDA1 /* AddEntryView.swift */, + 094086992B684842000DA980 /* AddEntryModel.swift */, + ); + path = Add; + sourceTree = ""; + }; 094A9FEC2629E6A3006E5605 /* Features */ = { isa = PBXGroup; children = ( @@ -475,9 +486,9 @@ 09644C5525C9858B000FFDA1 /* Entry */ = { isa = PBXGroup; children = ( + 094086972B684837000DA980 /* Add */, 09644C7725C985AE000FFDA1 /* Picture */, 09644C6725C9859C000FFDA1 /* UI */, - 09644C5725C98596000FFDA1 /* AddEntryView.swift */, 09644C5B25C98596000FFDA1 /* EntriesListView.swift */, 09644C5825C98596000FFDA1 /* EntriesView.swift */, 09644C5925C98596000FFDA1 /* EntryRowView.swift */, @@ -984,6 +995,7 @@ files = ( 09644C5C25C98596000FFDA1 /* WebView.swift in Sources */, 09644B5E25C98116000FFDA1 /* AppDelegate.swift in Sources */, + 0940869A2B684842000DA980 /* AddEntryModel.swift in Sources */, 09644D1825C98755000FFDA1 /* AppDelegateExtension.swift in Sources */, 09644BD825C9833E000FFDA1 /* DependencyInjection.swift in Sources */, 09644B5725C9810A000FFDA1 /* WallabagApp.swift in Sources */,