456 lines
18 KiB
Swift
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
|