Files
SwiftForge/SwiftForge/Utilities/PreviewMocks.swift
2025-07-05 14:38:17 -06:00

456 lines
18 KiB
Swift

//
// PreviewMocks.swift
// SwiftForge
//
// Created by Atridad Lahiji on 2025-07-04.
//
import Foundation
import SwiftUI
#if DEBUG
// MARK: - Mock Authentication Manager
class PreviewAuthenticationManager: ObservableObject {
@Published var isAuthenticated = true
@Published var isLoading = false
@Published var errorMessage: String?
@Published var currentUser: User?
@Published var serverURL = "https://github.com"
init() {
self.currentUser = PreviewData.sampleUser
}
func login(serverURL: String, token: String) async {}
func logout() {}
func checkAuthenticationStatus() {}
}
// MARK: - Mock Settings Manager
class PreviewSettingsManager: ObservableObject {
@Published var repositoryListStyle: String = "detailed"
@Published var showPrivateRepositories = true
@Published var showForkedRepositories = true
@Published var defaultRepositorySort: String = "updated"
@Published var theme: String = "system"
@Published var accentColor: String = "blue"
init() {}
}
// MARK: - Preview Extensions
extension PreviewData {
static let mockRepositories = [
Repository(
id: 1,
name: "SwiftForge",
fullName: "johndoe/SwiftForge",
description: "A powerful iOS Git client built with SwiftUI",
htmlUrl: "https://github.com/johndoe/SwiftForge",
cloneUrl: "https://github.com/johndoe/SwiftForge.git",
sshUrl: "git@github.com:johndoe/SwiftForge.git",
owner: sampleUser,
private: false,
fork: false,
template: false,
empty: false,
archived: false,
mirror: false,
size: 2048,
language: "Swift",
languagesUrl: "https://api.github.com/repos/johndoe/SwiftForge/languages",
forksCount: 25,
stargazersCount: 150,
watchersCount: 75,
openIssuesCount: 12,
openPrCounter: 5,
releaseCounter: 8,
defaultBranch: "main",
createdAt: Calendar.current.date(byAdding: .month, value: -6, to: Date()) ?? Date(),
updatedAt: Calendar.current.date(byAdding: .day, value: -2, to: Date()) ?? Date(),
permissions: nil,
hasIssues: true,
hasWiki: true,
hasPullRequests: true,
hasProjects: true,
hasReleases: true,
hasPackages: false,
hasActions: true,
topics: ["ios", "swift", "git", "mobile", "swiftui"],
avatarUrl: nil,
internalTracker: nil,
externalTracker: nil,
externalWiki: nil
),
Repository(
id: 2,
name: "swift-algorithms",
fullName: "johndoe/swift-algorithms",
description: "Commonly used sequence and collection algorithms for Swift",
htmlUrl: "https://github.com/johndoe/swift-algorithms",
cloneUrl: "https://github.com/johndoe/swift-algorithms.git",
sshUrl: "git@github.com:johndoe/swift-algorithms.git",
owner: sampleUser,
private: false,
fork: true,
template: false,
empty: false,
archived: false,
mirror: false,
size: 1024,
language: "Swift",
languagesUrl: "https://api.github.com/repos/johndoe/swift-algorithms/languages",
forksCount: 8,
stargazersCount: 45,
watchersCount: 20,
openIssuesCount: 3,
openPrCounter: 1,
releaseCounter: 12,
defaultBranch: "main",
createdAt: Calendar.current.date(byAdding: .month, value: -12, to: Date())
?? Date(),
updatedAt: Calendar.current.date(byAdding: .hour, value: -6, to: Date()) ?? Date(),
permissions: nil,
hasIssues: true,
hasWiki: false,
hasPullRequests: true,
hasProjects: false,
hasReleases: true,
hasPackages: true,
hasActions: true,
topics: ["swift", "algorithms", "data-structures"],
avatarUrl: nil,
internalTracker: nil,
externalTracker: nil,
externalWiki: nil
),
Repository(
id: 3,
name: "private-config",
fullName: "johndoe/private-config",
description: "Personal configuration files and scripts",
htmlUrl: "https://github.com/johndoe/private-config",
cloneUrl: "https://github.com/johndoe/private-config.git",
sshUrl: "git@github.com:johndoe/private-config.git",
owner: sampleUser,
private: true,
fork: false,
template: false,
empty: false,
archived: false,
mirror: false,
size: 512,
language: "Shell",
languagesUrl: "https://api.github.com/repos/johndoe/private-config/languages",
forksCount: 0,
stargazersCount: 0,
watchersCount: 1,
openIssuesCount: 0,
openPrCounter: 0,
releaseCounter: 0,
defaultBranch: "main",
createdAt: Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date(),
updatedAt: Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date(),
permissions: nil,
hasIssues: false,
hasWiki: false,
hasPullRequests: false,
hasProjects: false,
hasReleases: false,
hasPackages: false,
hasActions: false,
topics: ["dotfiles", "config", "personal"],
avatarUrl: nil,
internalTracker: nil,
externalTracker: nil,
externalWiki: nil
),
]
}
// MARK: - Mock View Components
struct MockRepositoriesView: View {
@State private var repositories = PreviewData.mockRepositories
@State private var searchText = ""
@State private var showingFilter = false
@State private var showingCreateRepository = false
var body: some View {
VStack(spacing: 0) {
CustomHeader(
title: "Repositories",
trailingContent: {
AnyView(
HStack(spacing: 12) {
Button(action: { showingCreateRepository = true }) {
Image(systemName: "plus")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.primary)
}
Menu {
Button("Filter") {
showingFilter = true
}
Button("Sort") {}
} label: {
Image(systemName: "ellipsis.circle")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.primary)
}
}
)
}
)
List(repositories) { repository in
VStack(alignment: .leading, spacing: 8) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(repository.name)
.font(.headline)
.foregroundColor(.primary)
if let description = repository.description {
Text(description)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
Spacer()
VStack(alignment: .trailing) {
if repository.private {
Image(systemName: "lock")
.font(.caption)
.foregroundColor(.secondary)
}
if repository.fork {
Image(systemName: "tuningfork")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
HStack(spacing: 16) {
if let language = repository.language {
HStack(spacing: 4) {
Circle()
.fill(colorForLanguage(language))
.frame(width: 8, height: 8)
Text(language)
.font(.caption)
.foregroundColor(.secondary)
}
}
HStack(spacing: 4) {
Image(systemName: "star")
.font(.caption)
Text("\(repository.stargazersCount)")
.font(.caption)
}
.foregroundColor(.secondary)
HStack(spacing: 4) {
Image(systemName: "tuningfork")
.font(.caption)
Text("\(repository.forksCount)")
.font(.caption)
}
.foregroundColor(.secondary)
Spacer()
Text(repository.updatedAt.timeAgoSince())
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding(.vertical, 4)
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
}
.listStyle(PlainListStyle())
}
.navigationBarHidden(true)
.searchable(text: $searchText, prompt: "Search repositories")
}
}
struct MockIssuesView: View {
var body: some View {
VStack(spacing: 0) {
CustomHeader(
title: "Issues",
trailingContent: {
AnyView(
Menu {
Button("Open") {}
Button("Closed") {}
} label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.primary)
}
)
}
)
EmptyStateView(
systemImage: "exclamationmark.triangle",
title: "No Repository Selected",
description: "Select a repository to view its issues",
actionTitle: "Browse Repositories",
action: {}
)
}
.navigationBarHidden(true)
}
}
struct MockNotificationsView: View {
var body: some View {
VStack(spacing: 0) {
CustomHeader(
title: "Notifications",
trailingContent: {
AnyView(
HStack(spacing: 12) {
Button("All") {}
.font(.caption)
Button(action: {}) {
Image(systemName: "checkmark.circle")
}
}
)
}
)
EmptyStateView(
systemImage: "bell.slash",
title: "No Notifications",
description: "You're all caught up! No new notifications.",
actionTitle: nil,
action: nil
)
}
.navigationBarHidden(true)
}
}
struct MockProfileView: View {
var body: some View {
VStack(spacing: 0) {
CustomHeader(
title: "Profile",
trailingContent: {
AnyView(
Menu {
Button("Edit Profile") {}
Button("Settings") {}
Divider()
Button("Sign Out", role: .destructive) {}
} label: {
Image(systemName: "ellipsis.circle")
.font(.system(size: 18, weight: .medium))
.foregroundColor(.primary)
}
)
}
)
ScrollView {
VStack(spacing: 20) {
// Profile Header
VStack(spacing: 12) {
AsyncImage(url: URL(string: PreviewData.sampleUser.avatarUrl)) {
phase in
switch phase {
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
case .failure(_):
Image(systemName: "person.circle.fill")
.font(.system(size: 60))
.foregroundColor(.secondary)
case .empty:
ProgressView()
@unknown default:
EmptyView()
}
}
.frame(width: 80, height: 80)
.clipShape(Circle())
VStack(spacing: 4) {
Text(
PreviewData.sampleUser.fullName ?? PreviewData.sampleUser.login
)
.font(.title2)
.fontWeight(.bold)
Text("@\(PreviewData.sampleUser.login)")
.font(.subheadline)
.foregroundColor(.secondary)
if let description = PreviewData.sampleUser.description {
Text(description)
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
}
}
.padding()
// Stats
HStack(spacing: 20) {
VStack {
Text("\(PreviewData.sampleUser.followersCount)")
.font(.headline)
.fontWeight(.bold)
Text("Followers")
.font(.caption)
.foregroundColor(.secondary)
}
VStack {
Text("\(PreviewData.sampleUser.followingCount)")
.font(.headline)
.fontWeight(.bold)
Text("Following")
.font(.caption)
.foregroundColor(.secondary)
}
VStack {
Text("\(PreviewData.sampleUser.starredReposCount)")
.font(.headline)
.fontWeight(.bold)
Text("Stars")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
Spacer()
}
}
}
.navigationBarHidden(true)
}
}
#endif