272 lines
8.3 KiB
Swift
272 lines
8.3 KiB
Swift
//
|
|
// MontanaContractsApp.swift
|
|
// Montana Contracts — Bitcoin Pizza Style
|
|
//
|
|
// App 3 of 3: Contracts + Voting + Escrow
|
|
// Bundle ID: network.montana.contracts
|
|
//
|
|
|
|
import SwiftUI
|
|
import MontanaCore
|
|
|
|
@main
|
|
struct MontanaContractsApp: App {
|
|
init() {
|
|
Montana.initialize()
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ContractsMainView()
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ContractsMainView: View {
|
|
@State private var contracts: [ContractItem] = []
|
|
@State private var showNewContract = false
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 0) {
|
|
if contracts.isEmpty {
|
|
EmptyContractsView()
|
|
} else {
|
|
ScrollView {
|
|
LazyVStack(spacing: 12) {
|
|
ForEach(contracts) { contract in
|
|
ContractCardView(contract: contract)
|
|
}
|
|
}
|
|
.padding()
|
|
}
|
|
}
|
|
}
|
|
.background(MontanaTheme.background)
|
|
.navigationTitle("Контракты")
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button {
|
|
MontanaLinks.openWallet()
|
|
} label: {
|
|
Image(systemName: "creditcard")
|
|
}
|
|
.foregroundColor(MontanaTheme.primary)
|
|
}
|
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button {
|
|
showNewContract = true
|
|
} label: {
|
|
Image(systemName: "plus.circle.fill")
|
|
}
|
|
.foregroundColor(MontanaTheme.primary)
|
|
}
|
|
}
|
|
.sheet(isPresented: $showNewContract) {
|
|
NewContractView()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct EmptyContractsView: View {
|
|
var body: some View {
|
|
VStack(spacing: 20) {
|
|
Image(systemName: "doc.text")
|
|
.font(.system(size: 60))
|
|
.foregroundColor(.secondary)
|
|
|
|
Text("Контрактов пока нет")
|
|
.font(.headline)
|
|
|
|
Text("Создай контракт через Юнону — она проведёт тебя через процесс")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 40)
|
|
|
|
Button {
|
|
MontanaLinks.openJunona(message: "Хочу создать контракт")
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "bubble.left.fill")
|
|
Text("Открыть Юнону")
|
|
}
|
|
.padding()
|
|
.background(MontanaTheme.primary)
|
|
.cornerRadius(12)
|
|
.foregroundColor(.white)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ContractItem: Identifiable {
|
|
let id: String
|
|
let amount: Double
|
|
let description: String
|
|
let status: Status
|
|
let votes: Int
|
|
let requiredVotes: Int
|
|
|
|
enum Status: String {
|
|
case draft = "DRAFT"
|
|
case voting = "VOTING"
|
|
case pending = "PENDING"
|
|
case accepted = "ACCEPTED"
|
|
case completed = "COMPLETED"
|
|
case rejected = "REJECTED"
|
|
|
|
var color: Color {
|
|
switch self {
|
|
case .draft: return .secondary
|
|
case .voting: return MontanaTheme.warning
|
|
case .pending: return MontanaTheme.primary
|
|
case .accepted, .completed: return MontanaTheme.success
|
|
case .rejected: return MontanaTheme.error
|
|
}
|
|
}
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .draft: return "Черновик"
|
|
case .voting: return "Голосование"
|
|
case .pending: return "Ожидание"
|
|
case .accepted: return "Принят"
|
|
case .completed: return "Завершён"
|
|
case .rejected: return "Отклонён"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ContractCardView: View {
|
|
let contract: ContractItem
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HStack {
|
|
Text(contract.status.displayName)
|
|
.font(.caption)
|
|
.fontWeight(.medium)
|
|
.padding(.horizontal, 12)
|
|
.padding(.vertical, 6)
|
|
.background(contract.status.color)
|
|
.cornerRadius(8)
|
|
|
|
Spacer()
|
|
|
|
Text("#\(contract.id)")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
HStack(alignment: .lastTextBaseline) {
|
|
Text("\(Int(contract.amount))")
|
|
.font(.system(size: 32, weight: .bold, design: .rounded))
|
|
Text("Ɉ")
|
|
.font(.title3)
|
|
.foregroundColor(MontanaTheme.primary)
|
|
}
|
|
|
|
Text(contract.description)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.lineLimit(2)
|
|
|
|
if contract.status == .voting {
|
|
VotingProgressView(current: contract.votes, required: contract.requiredVotes)
|
|
}
|
|
}
|
|
.padding()
|
|
.background(MontanaTheme.cardBackground)
|
|
.cornerRadius(16)
|
|
}
|
|
}
|
|
|
|
struct VotingProgressView: View {
|
|
let current: Int
|
|
let required: Int
|
|
|
|
var progress: Double {
|
|
guard required > 0 else { return 0 }
|
|
return Double(current) / Double(required)
|
|
}
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
HStack {
|
|
Text("Голосование")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
Spacer()
|
|
Text("\(current)/\(required)")
|
|
.font(.caption)
|
|
.fontWeight(.medium)
|
|
}
|
|
|
|
GeometryReader { geo in
|
|
ZStack(alignment: .leading) {
|
|
RoundedRectangle(cornerRadius: 4)
|
|
.fill(Color.secondary.opacity(0.3))
|
|
RoundedRectangle(cornerRadius: 4)
|
|
.fill(MontanaTheme.warning)
|
|
.frame(width: geo.size.width * progress)
|
|
}
|
|
}
|
|
.frame(height: 8)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct NewContractView: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack(spacing: 24) {
|
|
Image(systemName: "doc.badge.plus")
|
|
.font(.system(size: 60))
|
|
.foregroundColor(MontanaTheme.primary)
|
|
|
|
Text("Создание контракта")
|
|
.font(.title2)
|
|
.fontWeight(.bold)
|
|
|
|
Text("Контракты создаются через Юнону. Она проверит условия и выступит арбитром.")
|
|
.multilineTextAlignment(.center)
|
|
.foregroundColor(.secondary)
|
|
.padding(.horizontal)
|
|
|
|
Button {
|
|
MontanaLinks.openJunona(message: "Хочу создать контракт")
|
|
dismiss()
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "bubble.left.fill")
|
|
Text("Перейти к Юноне")
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.background(MontanaTheme.primary)
|
|
.cornerRadius(12)
|
|
.foregroundColor(.white)
|
|
}
|
|
.padding(.horizontal)
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.top, 40)
|
|
.background(MontanaTheme.background)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button("Закрыть") { dismiss() }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|