Skip to content

Commit 5610eab

Browse files
committed
Rewrite phase 2: split EVCore, protocols, AppEnvironment injection
- Split EVCore.swift → BundleIdentifiers + ContainerPaths + AppSettingsStore (shim stays for compatibility, will be removed in phase 3) - Add protocol seams: TunnelControlling, ConfigurationRepository, AppSettingsProviding - AppEnvironment assembles all singletons, injects via .environmentObject - All Views migrated to @EnvironmentObject — no more .shared in the UI layer
1 parent 1e6063b commit 5610eab

19 files changed

Lines changed: 345 additions & 171 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ All notable changes to **decoder sec.** are documented here.
1717
- In-app log console
1818
- Signed TestFlight builds
1919

20+
## [0.1.0-beta.14] — 2026-08-12
21+
22+
### Rewrite Phase 2 — зависимости и протоколы
23+
- `EVCore.swift` разбит на три модуля: `BundleIdentifiers`, `ContainerPaths`, `AppSettingsStore`
24+
- Введены протоколы-швы: `TunnelControlling`, `ConfigurationRepository`, `AppSettingsProviding`
25+
- `AppEnvironment` — единая точка сборки зависимостей, заменяет прямые `.shared` во всех Views
26+
- Views (Home, Content, Configs, Settings, Editor) переведены на `@EnvironmentObject` инжекцию
27+
- `DecoderSecApp` применяет `AppEnvironment.modifier` на корневой сцене
28+
2029
## [0.1.0-beta.13] — 2026-08-12
2130

2231
### Changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// AppEnvironment.swift
3+
// DecoderSec/App
4+
//
5+
// Single assembly point: builds every singleton once, exposes them
6+
// via .environmentObject / @EnvironmentObject so no View ever reaches
7+
// for .shared directly. Phase 2 rewrite.
8+
//
9+
10+
import SwiftUI
11+
12+
/// Owns the app's singletons and injects them into the SwiftUI tree.
13+
/// Add it to the root scene modifier chain once:
14+
///
15+
/// WindowGroup { ContentView() }
16+
/// .modifier(AppEnvironment.modifier)
17+
///
18+
@MainActor
19+
final class AppEnvironment: ObservableObject {
20+
21+
// MARK: - Singletons (assembled once here, injected everywhere else)
22+
23+
let tunnel = TunnelManager.shared
24+
let store = ConfigurationStore.shared
25+
let appState = AppState.shared
26+
let deepLinks = DeepLinkCenter.shared
27+
let routing = RoutingProfileStore.shared
28+
29+
// MARK: - Singleton (environment modifier)
30+
31+
static let shared = AppEnvironment()
32+
33+
private init() {}
34+
35+
/// Apply to the root `WindowGroup` scene.
36+
struct Modifier: ViewModifier {
37+
@ObservedObject private var env = AppEnvironment.shared
38+
39+
func body(content: Content) -> some View {
40+
content
41+
.environmentObject(env.tunnel)
42+
.environmentObject(env.store)
43+
.environmentObject(env.appState)
44+
.environmentObject(env.deepLinks)
45+
.environmentObject(env.routing)
46+
}
47+
}
48+
49+
static var modifier: Modifier { Modifier() }
50+
}

‎DecoderSec/App/ContentView.swift‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import NetworkExtension
99
import SwiftUI
1010

1111
struct ContentView: View {
12-
@ObservedObject private var tunnel = TunnelManager.shared
13-
@ObservedObject private var store = ConfigurationStore.shared
12+
@EnvironmentObject private var tunnel: TunnelManager
13+
@EnvironmentObject private var store: ConfigurationStore
1414
@EnvironmentObject private var deepLinks: DeepLinkCenter
1515
@State private var minimized: Bool = false
1616

@@ -111,9 +111,9 @@ struct ContentView: View {
111111
}
112112

113113
private struct RunningRootView: View {
114-
@ObservedObject private var store = ConfigurationStore.shared
115-
@ObservedObject private var appState = AppState.shared
116-
@ObservedObject private var tunnel = TunnelManager.shared
114+
@EnvironmentObject private var store: ConfigurationStore
115+
@EnvironmentObject private var appState: AppState
116+
@EnvironmentObject private var tunnel: TunnelManager
117117
@State private var pulse = false
118118

119119
var body: some View {

‎DecoderSec/App/DecoderSecApp.swift‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@
22
// DecoderSecApp.swift
33
// DecoderSec
44
//
5-
// Created by NodePassProject on 5/2/26.
6-
//
75

86
import SwiftUI
97

108
@main
119
struct DecoderSecApp: App {
12-
@ObservedObject private var deepLinks = DeepLinkCenter.shared
1310

1411
init() {
1512
_ = PersistenceController.shared
@@ -18,13 +15,13 @@ struct DecoderSecApp: App {
1815
var body: some Scene {
1916
WindowGroup {
2017
ContentView()
21-
.environmentObject(deepLinks)
18+
.modifier(AppEnvironment.modifier)
2219
.onOpenURL { url in
23-
deepLinks.handle(url: url)
20+
AppEnvironment.shared.deepLinks.handle(url: url)
2421
}
2522
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
2623
if let url = activity.webpageURL {
27-
deepLinks.handle(url: url)
24+
AppEnvironment.shared.deepLinks.handle(url: url)
2825
}
2926
}
3027
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// AppSettingsProviding.swift
3+
// DecoderSec/Core/Protocols
4+
//
5+
// Protocol seam for observable app settings — separates the SwiftUI binding
6+
// layer from the raw UserDefaults storage. Phase 2 rewrite.
7+
//
8+
9+
import Foundation
10+
11+
@MainActor
12+
protocol AppSettingsProviding: AnyObject, ObservableObject {
13+
var alwaysOnEnabled: Bool { get set }
14+
var dnsServers: [String] { get set }
15+
var tunnelIncludeAllNetworks: Bool { get set }
16+
var tunnelIncludeLocalNetworks: Bool { get set }
17+
var tunnelIncludeAPNs: Bool { get set }
18+
var tunnelIncludeCellularServices: Bool { get set }
19+
var useZashboardEnabled: Bool { get set }
20+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// ConfigurationRepository.swift
3+
// DecoderSec/Core/Protocols
4+
//
5+
// Protocol seam for config storage — lets Views test against a fake store.
6+
// Phase 2 rewrite.
7+
//
8+
9+
import Foundation
10+
11+
@MainActor
12+
protocol ConfigurationRepository: AnyObject, ObservableObject {
13+
var configurations: [Configuration] { get }
14+
var storeError: String? { get }
15+
var selectedCore: CoreType { get set }
16+
var activeIDByCoreType: [CoreType: UUID] { get }
17+
var active: Configuration? { get }
18+
var configurationsForSelectedCore: [Configuration] { get }
19+
20+
@discardableResult
21+
func create(name: String, type: CoreType, content: String, sourceURL: String?) -> Configuration?
22+
func update(_ config: Configuration, name: String)
23+
func update(_ config: Configuration, content: String)
24+
func delete(_ config: Configuration)
25+
func setActive(_ config: Configuration)
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// TunnelControlling.swift
3+
// DecoderSec/Core/Protocols
4+
//
5+
// Protocol seam for the tunnel lifecycle — lets Views and DeepLinkCenter
6+
// depend on an abstraction instead of TunnelManager.shared directly.
7+
// Phase 2 rewrite.
8+
//
9+
10+
import Foundation
11+
import NetworkExtension
12+
13+
@MainActor
14+
protocol TunnelControlling: AnyObject, ObservableObject {
15+
var status: NEVPNStatus { get }
16+
var isReady: Bool { get }
17+
var coreRunning: Bool { get }
18+
var lastError: String? { get }
19+
var pendingReconnect: Bool { get }
20+
21+
func setEnabled(_ on: Bool, configuration: Configuration?) async
22+
func reconnect() async
23+
func applyAlwaysOn(_ enabled: Bool) async
24+
func clearLastError()
25+
}

‎DecoderSec/Features/Configurations/ConfigurationsView.swift‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import SwiftUI
99
import UniformTypeIdentifiers
1010

1111
struct ConfigurationsView: View {
12-
@ObservedObject private var store = ConfigurationStore.shared
13-
@ObservedObject private var tunnel = TunnelManager.shared
12+
@EnvironmentObject private var store: ConfigurationStore
13+
@EnvironmentObject private var tunnel: TunnelManager
1414
@State private var pendingDelete: Configuration?
1515
@State private var blockedAlert = false
1616
@State private var fileImporting = false

‎DecoderSec/Features/Configurations/SubscribeSheet.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SwiftUI
99

1010
struct SubscribeSheet: View {
1111
@Environment(\.dismiss) private var dismiss
12-
@ObservedObject private var deepLinks = DeepLinkCenter.shared
12+
@EnvironmentObject private var deepLinks: DeepLinkCenter
1313
@State private var raw = ""
1414
@State private var localError: String?
1515
@FocusState private var focused: Bool

‎DecoderSec/Features/Editor/ConfigEditorScreen.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import SwiftUI
99

1010
struct ConfigEditorScreen: View {
1111
@ObservedObject var configuration: Configuration
12-
@ObservedObject private var store = ConfigurationStore.shared
12+
@EnvironmentObject private var store: ConfigurationStore
1313
@State private var draft: String = ""
1414

1515
var body: some View {

0 commit comments

Comments
 (0)