montana/macOS/MontanaPresence/PrivateView.swift

412 lines
15 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import CryptoKit
/// Montana Private Приватные кошельки и зашифрованные транзакции
/// Zero-knowledge proof, stealth addresses, полная анонимность
struct PrivateView: View {
@EnvironmentObject var engine: PresenceEngine
@State private var privateNickname = ""
@State private var isPurchasing = false
@State private var purchaseStatus = ""
@State private var hasPrivateWallet = false
@State private var privateBalance = 0
@State private var privateAddress = ""
@State private var showSidebar = false
// Montana colors (from website efir.org)
private let gold = Color(red: 0.83, green: 0.69, blue: 0.22) // #D4AF37
private let goldLight = Color(red: 0.94, green: 0.82, blue: 0.38) // #F0D060
private let goldDark = Color(red: 0.55, green: 0.41, blue: 0.08) // #8B6914
private let bgDark = Color(red: 0.04, green: 0.04, blue: 0.04) // #0a0a0a
private let textBeige = Color(red: 0.91, green: 0.88, blue: 0.82) // #e8e0d0
private let crimson = Color(red: 0.55, green: 0.10, blue: 0.10) // #8B1A1A
var body: some View {
ZStack(alignment: .leading) {
VStack(spacing: 0) {
// BURGER MENU BUTTON
HStack {
Button(action: { withAnimation(.easeInOut(duration: 0.3)) { showSidebar = true } }) {
Image(systemName: "line.3.horizontal")
.font(.system(size: 18, weight: .medium))
.foregroundColor(gold)
.padding(8)
}
.buttonStyle(.plain)
Spacer()
}
.padding(.horizontal, 12)
.padding(.top, 8)
// Header
header
Divider()
// Main content
ScrollView {
VStack(spacing: 24) {
if hasPrivateWallet {
// Private wallet card
privateWalletCard
} else {
// Purchase card
purchaseCard
}
// Info section
infoSection
}
.padding()
}
}
.background(Color(NSColor.windowBackgroundColor))
.onAppear {
loadPrivateWallet()
}
// Sidebar overlay
if showSidebar {
Color.black.opacity(0.3)
.ignoresSafeArea()
.onTapGesture {
withAnimation(.easeInOut(duration: 0.3)) {
showSidebar = false
}
}
SharedSidebar(isVisible: $showSidebar)
.transition(.move(edge: .leading))
}
}
}
// MARK: - Header
private var header: some View {
HStack(spacing: 12) {
// Private icon (gold style)
Circle()
.fill(
LinearGradient(
colors: [goldDark, gold],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.frame(width: 40, height: 40)
.overlay(
Image(systemName: "eye.slash.fill")
.font(.system(size: 20))
.foregroundColor(.black)
)
VStack(alignment: .leading, spacing: 2) {
Text("Приват")
.font(.headline)
.foregroundColor(textBeige)
Text("Зашифрованные транзакции")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
// Privacy badge (gold)
HStack(spacing: 4) {
Image(systemName: "lock.shield.fill")
.font(.system(size: 10))
Text("ZERO-KNOWLEDGE")
.font(.system(size: 8, weight: .bold))
}
.foregroundColor(gold)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(gold.opacity(0.1))
.cornerRadius(8)
}
.padding()
}
// MARK: - Purchase Card
private var purchaseCard: some View {
VStack(spacing: 20) {
Image(systemName: "eye.slash.circle.fill")
.font(.system(size: 60))
.foregroundColor(gold)
Text("🔐 Приватный кошелёк")
.font(.title)
.fontWeight(.bold)
.foregroundColor(textBeige)
Text("Покупка приватного ника открывает доступ к зашифрованным транзакциям. Баланс, история, адрес — всё скрыто от blockchain explorer.")
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.lineSpacing(4)
Divider()
// Nickname input
VStack(alignment: .leading, spacing: 8) {
Text("Выбери приватный ник")
.font(.callout)
.fontWeight(.semibold)
HStack(spacing: 8) {
TextField("nickname", text: $privateNickname)
.textFieldStyle(.plain)
.font(.system(size: 14, design: .monospaced))
.padding(10)
.background(Color(NSColor.controlBackgroundColor))
.cornerRadius(8)
.disabled(isPurchasing)
Text("@private.montana")
.font(.system(size: 14, design: .monospaced))
.foregroundColor(.secondary)
}
if !purchaseStatus.isEmpty {
Text(purchaseStatus)
.font(.caption)
.foregroundColor(purchaseStatus.contains("") ? .green : .red)
}
}
// Purchase button (gold gradient like website)
Button(action: purchasePrivateNick) {
HStack(spacing: 8) {
if isPurchasing {
ProgressView()
.controlSize(.small)
.scaleEffect(0.8)
} else {
Image(systemName: "cart.fill")
.font(.system(size: 14))
}
Text("Купить приватный ник")
.font(.system(size: 14, weight: .semibold))
}
.foregroundColor(.black)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(
LinearGradient(
colors: [goldDark, gold, goldLight],
startPoint: .leading,
endPoint: .trailing
)
)
.cornerRadius(12)
.shadow(color: gold.opacity(0.4), radius: 8, x: 0, y: 4)
}
.buttonStyle(.plain)
.disabled(privateNickname.isEmpty || isPurchasing)
}
.frame(maxWidth: .infinity)
.padding(40)
.background(gold.opacity(0.03))
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(gold.opacity(0.08), lineWidth: 1)
)
}
// MARK: - Private Wallet Card
private var privateWalletCard: some View {
VStack(spacing: 16) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Приватный кошелёк")
.font(.system(size: 13, weight: .semibold))
.foregroundColor(.secondary)
Text("\(privateNickname)@private.montana")
.font(.system(size: 16, weight: .bold, design: .monospaced))
.foregroundColor(gold)
}
Spacer()
Image(systemName: "checkmark.shield.fill")
.font(.system(size: 24))
.foregroundColor(.green)
}
Divider()
// Balance (encrypted)
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("БАЛАНС")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundColor(.secondary)
Text("█████ Ɉ")
.font(.system(size: 20, weight: .bold, design: .monospaced))
.foregroundColor(gold)
}
Spacer()
Text("🔒 ЗАШИФРОВАНО")
.font(.system(size: 9, weight: .bold))
.foregroundColor(crimson)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(crimson.opacity(0.1))
.cornerRadius(6)
}
// Stealth address
VStack(alignment: .leading, spacing: 4) {
Text("STEALTH ADDRESS")
.font(.system(size: 9, weight: .bold, design: .monospaced))
.foregroundColor(.secondary)
Text(privateAddress.isEmpty ? "████████████████████████████" : privateAddress)
.font(.system(size: 10, design: .monospaced))
.foregroundColor(.secondary)
.lineLimit(1)
}
// Private actions (gold style)
HStack(spacing: 8) {
Button(action: {}) {
HStack(spacing: 4) {
Text("Ɉ")
.font(.system(size: 12, weight: .bold))
Image(systemName: "arrow.up")
.font(.system(size: 10, weight: .bold))
Text("Отправить")
.font(.system(size: 11, weight: .semibold))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(gold.opacity(0.15))
.foregroundColor(gold)
.cornerRadius(8)
}
.buttonStyle(.plain)
Button(action: {}) {
HStack(spacing: 4) {
Text("Ɉ")
.font(.system(size: 12, weight: .bold))
Image(systemName: "arrow.down")
.font(.system(size: 10, weight: .bold))
Text("Получить")
.font(.system(size: 11, weight: .semibold))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(goldDark.opacity(0.15))
.foregroundColor(goldLight)
.cornerRadius(8)
}
.buttonStyle(.plain)
}
}
.padding(16)
.background(gold.opacity(0.03))
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(gold.opacity(0.25), lineWidth: 1)
)
.shadow(color: gold.opacity(0.1), radius: 10, x: 0, y: 4)
}
// MARK: - Info Section
private var infoSection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("💡 Как работает приват")
.font(.title3)
.fontWeight(.semibold)
.foregroundColor(textBeige)
VStack(alignment: .leading, spacing: 8) {
InfoRow(icon: "🔐", text: "Zero-knowledge proof — никто не видит баланс")
InfoRow(icon: "🎭", text: "Stealth addresses — новый адрес для каждой транзакции")
InfoRow(icon: "🔒", text: "Ring signatures — невозможно отследить отправителя")
InfoRow(icon: "👻", text: "Не отображается в blockchain explorer")
InfoRow(icon: "💰", text: "Покупка приватного ника = доступ навсегда")
}
}
.padding()
.background(gold.opacity(0.05))
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(gold.opacity(0.08), lineWidth: 1)
)
}
// MARK: - Actions
private func loadPrivateWallet() {
let savedNick = UserDefaults.standard.string(forKey: "private_wallet_nick") ?? ""
if !savedNick.isEmpty {
privateNickname = savedNick
hasPrivateWallet = true
privateAddress = UserDefaults.standard.string(forKey: "private_wallet_address") ?? ""
} else {
hasPrivateWallet = false
}
}
private func purchasePrivateNick() {
guard !privateNickname.isEmpty else { return }
isPurchasing = true
purchaseStatus = ""
let nick = privateNickname.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
guard nick.range(of: "^[a-z0-9_-]+$", options: .regularExpression) != nil else {
purchaseStatus = "❌ Только латиница, цифры, _ и -"
isPurchasing = false
return
}
// Generate stealth address from nickname + wallet address using SHA256
let seed = "\(nick)@private.montana:\(engine.address ?? "local"):\(Date().timeIntervalSince1970)"
let digest = SHA256.hash(data: Data(seed.utf8))
let stealthAddr = "stealth_" + digest.compactMap { String(format: "%02x", $0) }.joined().prefix(40)
privateAddress = String(stealthAddr)
privateNickname = nick
hasPrivateWallet = true
purchaseStatus = "✅ Приватный ник куплен!"
UserDefaults.standard.set(nick, forKey: "private_wallet_nick")
UserDefaults.standard.set(privateAddress, forKey: "private_wallet_address")
isPurchasing = false
}
}
// MARK: - Supporting Views
private struct InfoRow: View {
let icon: String
let text: String
var body: some View {
HStack(spacing: 8) {
Text(icon)
.font(.body)
Text(text)
.font(.callout)
.foregroundColor(.secondary)
}
}
}
// MARK: - Preview
#Preview {
PrivateView()
.environmentObject(PresenceEngine.shared)
.frame(width: 600, height: 500)
}