Created
August 5, 2025 21:56
-
-
Save SoundBlaster/b8436977a3292d615220e780553dd6e4 to your computer and use it in GitHub Desktop.
GPT-OSS-20B SwiftUI App Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ────────────────────────────────────────────────────────────── | |
// 1️⃣ Project‑Structure (all files in one block for demo) | |
// ────────────────────────────────────────────────────────────── | |
// | |
// AppDelegate.swift – UIApplicationDelegate (optional) | |
// SceneDelegate.swift – UIWindowSceneDelegate (if needed) | |
// MainApp.swift – @main entry point | |
// Models/CounterEntity.swift | |
// Domain/Protocols/CounterRepositoryProtocol.swift | |
// Domain/UseCases/IncrementCounter.swift | |
// Domain/UseCases/ResetCounter.swift | |
// Data/UserDefaultsCounterRepository.swift | |
// Presentation/Store/CounterStore.swift | |
// Presentation/ViewModel/CounterViewModel.swift | |
// Presentation/View/CounterView.swift | |
// Presentation/View/OnboardingView.swift | |
// Presentation/Coordinator/AppCoordinator.swift | |
// Presentation/Theme/ColorPalette.swift | |
// Utils/Logger.swift | |
// Utils/Extensions/UIHostingController+Coordinator.swift | |
// | |
// ────────────────────────────────────────────────────────────── | |
import SwiftUI | |
import Combine | |
// MARK: - 1️⃣ Models --------------------------------------------------------- | |
struct CounterEntity { | |
var value: Int = 0 | |
} | |
// MARK: - 2️⃣ Domain Layer --------------------------------------------------- | |
protocol CounterRepositoryProtocol: AnyObject { | |
var counterPublisher: Published<CounterEntity>.Publisher { get } | |
func increment() throws | |
func reset() | |
} | |
enum CounterError: Error, LocalizedError { | |
case failedToPersist | |
var errorDescription: String? { | |
switch self { | |
case .failedToPersist: return "Could not persist counter value." | |
} | |
} | |
} | |
// MARK: - 3️⃣ Use Cases ------------------------------------------------------ | |
final class IncrementCounter { | |
private let repository: CounterRepositoryProtocol | |
init(repository: CounterRepositoryProtocol) { self.repository = repository } | |
func execute() throws { | |
try repository.increment() | |
} | |
} | |
final class ResetCounter { | |
private let repository: CounterRepositoryProtocol | |
init(repository: CounterRepositoryProtocol) { self.repository = repository } | |
func execute() { | |
repository.reset() | |
} | |
} | |
// MARK: - 4️⃣ Data Layer ----------------------------------------------------- | |
final class UserDefaultsCounterRepository: CounterRepositoryProtocol { | |
@Published private(set) var counter = CounterEntity() | |
private let key = "tap_counter" | |
private let logger = Logger.shared | |
init() { | |
let stored = UserDefaults.standard.integer(forKey: key) | |
self.counter.value = stored | |
} | |
var counterPublisher: Published<CounterEntity>.Publisher { $counter } | |
func increment() throws { | |
counter.value += 1 | |
guard persist() else { throw CounterError.failedToPersist } | |
} | |
func reset() { | |
counter.value = 0 | |
_ = persist() | |
} | |
private func persist() -> Bool { | |
UserDefaults.standard.set(counter.value, forKey: key) | |
// In normal usage this is effectively always true. | |
return true | |
} | |
} | |
// MARK: - 5️⃣ Presentation Layer – Store ------------------------------------ | |
final class CounterStore: ObservableObject { | |
// 1️⃣ Provide an initial value → _counter is ready. | |
@Published private(set) var counter = CounterEntity() | |
private let incrementUseCase: IncrementCounter | |
private let resetUseCase: ResetCounter | |
// <‑‑ Add a logger reference | |
private let logger = Logger.shared | |
init(repository: CounterRepositoryProtocol) { | |
self.incrementUseCase = IncrementCounter(repository: repository) | |
self.resetUseCase = ResetCounter(repository: repository) | |
// 2️⃣ Now we can safely subscribe. | |
repository.counterPublisher | |
.receive(on: DispatchQueue.main) | |
.assign(to: &$counter) // OK – $counter exists now | |
} | |
func increment() { | |
do { try incrementUseCase.execute() } | |
catch { | |
logger.error("Increment failed: $error)") // fixed interpolation | |
} | |
} | |
func reset() { resetUseCase.execute() } | |
} | |
// MARK: - 6️⃣ Presentation Layer – ViewModel -------------------------------- | |
final class CounterViewModel: ObservableObject { | |
@Published private(set) var counterValue: Int = 0 | |
private let store: CounterStore | |
private var cancellables = Set<AnyCancellable>() | |
init(store: CounterStore) { self.store = store } | |
func bind() { | |
store.$counter | |
.map(\.value) | |
.receive(on: DispatchQueue.main) | |
.assign(to: &$counterValue) | |
} | |
// Commands | |
func tapButtonPressed() { store.increment() } | |
func resetButtonPressed() { store.reset() } | |
} | |
// MARK: - 7️⃣ Presentation Layer – Views ------------------------------------ | |
struct CounterView: View { | |
@EnvironmentObject private var store: CounterStore | |
@StateObject private var vm = CounterViewModel(store: CounterStore(repository: UserDefaultsCounterRepository())) | |
init() { | |
_vm = StateObject(wrappedValue: CounterViewModel(store: CounterStore(repository: UserDefaultsCounterRepository()))) | |
} | |
var body: some View { | |
VStack(spacing: 40) { | |
Text("You tapped \(vm.counterValue) times") | |
.font(.largeTitle) | |
.fontWeight(.semibold) | |
HStack(spacing: 20) { | |
Button(action: vm.tapButtonPressed) { | |
Label("Tap me!", systemImage: "hand.tap.fill") | |
.font(.title2) | |
.padding() | |
.background(ColorPalette.primary.opacity(0.1)) | |
.cornerRadius(12) | |
} | |
Button(action: vm.resetButtonPressed) { | |
Label("Reset", systemImage: "arrow.counterclockwise") | |
.font(.title3) | |
.foregroundColor(.red) | |
.padding() | |
.background(Color.red.opacity(0.1)) | |
.cornerRadius(12) | |
} | |
} | |
} | |
.onAppear { vm.bind() } | |
.padding() | |
} | |
} | |
struct OnboardingView: View { | |
let onContinue: () -> Void | |
var body: some View { | |
VStack(spacing: 30) { | |
Text("Welcome to Tap Counter") | |
.font(.title) | |
.multilineTextAlignment(.center) | |
Button("Get Started") { onContinue() } | |
.buttonStyle(.borderedProminent) | |
} | |
.padding() | |
} | |
} | |
// MARK: - 8️⃣ Presentation Layer – Coordinator -------------------------------- | |
final class AppCoordinator: ObservableObject { | |
@Published var isFirstLaunch = true | |
let store: CounterStore | |
private let logger = Logger.shared | |
init() { | |
// Shared repository | |
let repo = UserDefaultsCounterRepository() | |
self.store = CounterStore(repository: repo) | |
// Check onboarding flag | |
if UserDefaults.standard.bool(forKey: "has_seen_onboarding") { | |
isFirstLaunch = false | |
} else { | |
logger.debug("First launch detected") | |
} | |
} | |
func completeOnboarding() { | |
UserDefaults.standard.set(true, forKey: "has_seen_onboarding") | |
withAnimation { isFirstLaunch = false } | |
} | |
} | |
// MARK: - 9️⃣ Presentation Layer – Theme ------------------------------------ | |
struct ColorPalette { | |
static let primary = Color.accentColor // or any system color | |
static let secondary = Color.secondary // e.g. .secondary, .systemBlue, etc. | |
} | |
// MARK: - 🔟 Utilities ------------------------------------------------------- | |
final class Logger { | |
static let shared = Logger() | |
private init() {} | |
func debug(_ message: String) { print("[DEBUG] $message)") } | |
func error(_ message: String) { print("[ERROR] $message)") } | |
} | |
/// A small helper that sets the hosting controller’s view background. | |
extension UIHostingController { | |
/// Sets a system background color on the host’s view. | |
func setSystemBackground() -> Self { | |
self.view.backgroundColor = .systemBackground | |
return self | |
} | |
} | |
// MARK: - 📦 Main App Entry -------------------------------------------------- | |
@main | |
struct TapCounterApp: App { | |
@StateObject private var coordinator = AppCoordinator() | |
var body: some Scene { | |
WindowGroup { | |
Group { | |
if coordinator.isFirstLaunch { | |
OnboardingView(onContinue: { coordinator.completeOnboarding() }) | |
.environmentObject(coordinator.store) | |
} else { | |
CounterView() | |
.environmentObject(coordinator.store) | |
} | |
} | |
.animation(.default, value: coordinator.isFirstLaunch) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment