import SwiftUI /// Shared Sidebar (Burger Menu) for all Montana views /// Clean release: only implemented & working features struct SharedSidebar: View { @Binding var isVisible: Bool @ObservedObject private var lang = LanguageManager.shared @ObservedObject private var sessionStore = ChatSessionStore.shared @State private var showLogoutConfirm = false @State private var chatsExpanded = true var body: some View { VStack(alignment: .leading, spacing: 0) { sidebarHeader Divider() ScrollView { VStack(alignment: .leading, spacing: 24) { junonaSection Divider() montanaSection Divider() exchangeSection Divider() settingsSection Divider() logoutSection } .padding() } } .frame(width: 280) .background(Color(NSColor.controlBackgroundColor).opacity(0.95)) .shadow(radius: 10) .alert(lang.isRu ? "\u{0412}\u{044B}\u{0439}\u{0442}\u{0438} \u{0438}\u{0437} Montana?" : "Log out of Montana?", isPresented: $showLogoutConfirm) { Button(lang.isRu ? "\u{0412}\u{044B}\u{0439}\u{0442}\u{0438}" : "Log Out", role: .destructive) { withAnimation { isVisible = false } CryptoManager.shared.deleteIdentity() } Button(lang.isRu ? "\u{041E}\u{0442}\u{043C}\u{0435}\u{043D}\u{0430}" : "Cancel", role: .cancel) {} } message: { Text(lang.isRu ? "\u{041A}\u{043B}\u{044E}\u{0447}\u{0438} \u{0431}\u{0443}\u{0434}\u{0443}\u{0442} \u{0443}\u{0434}\u{0430}\u{043B}\u{0435}\u{043D}\u{044B} \u{0438}\u{0437} Keychain. \u{0423}\u{0431}\u{0435}\u{0434}\u{0438}\u{0442}\u{0435}\u{0441}\u{044C}, \u{0447}\u{0442}\u{043E} \u{0432}\u{044B} \u{0441}\u{043E}\u{0445}\u{0440}\u{0430}\u{043D}\u{0438}\u{043B}\u{0438} seed-\u{0444}\u{0440}\u{0430}\u{0437}\u{0443} \u{0434}\u{043B}\u{044F} \u{0432}\u{043E}\u{0441}\u{0441}\u{0442}\u{0430}\u{043D}\u{043E}\u{0432}\u{043B}\u{0435}\u{043D}\u{0438}\u{044F}." : "Keys will be deleted from Keychain. Make sure you saved your seed phrase for recovery.") } } // MARK: - Header private var sidebarHeader: some View { HStack { if let logoPath = Bundle.main.path(forResource: "JunonaLogo", ofType: "jpg"), let nsImage = NSImage(contentsOfFile: logoPath) { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 32, height: 32) .clipShape(Circle()) .overlay( Circle() .stroke( LinearGradient( colors: [ Color(red: 0.83, green: 0.69, blue: 0.22), Color(red: 0.94, green: 0.82, blue: 0.38) ], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 1.5 ) ) } else { Circle() .fill( LinearGradient( colors: [ Color(red: 0.0, green: 0.83, blue: 1.0), Color(red: 0.48, green: 0.18, blue: 1.0) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .frame(width: 32, height: 32) .overlay( Text("\u{042E}") .font(.system(size: 16, weight: .bold)) .foregroundColor(.white) ) } VStack(alignment: .leading, spacing: 2) { Text("Montana Protocol") .font(.headline) Text("\u{0248}") .font(.caption2) .foregroundColor(.secondary) } Spacer() Button(action: { withAnimation { isVisible = false } }) { Image(systemName: "xmark") .font(.system(size: 14)) .foregroundColor(.secondary) } .buttonStyle(.plain) } .padding() } // MARK: - Junona Section (with collapsible chats) private var junonaSection: some View { VStack(alignment: .leading, spacing: 12) { Text("Junona AI") .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) // New chat button Button(action: { _ = sessionStore.createNewSession() NotificationCenter.default.post(name: .switchToTab, object: nil, userInfo: ["tab": 0]) withAnimation { isVisible = false } }) { HStack(spacing: 8) { Image(systemName: "plus.message") .font(.system(size: 14)) .foregroundColor(Color(red: 0.0, green: 0.83, blue: 1.0)) .frame(width: 20) Text(lang.isRu ? "\u{041D}\u{043E}\u{0432}\u{044B}\u{0439} \u{0447}\u{0430}\u{0442}" : "New Chat") .font(.callout) .foregroundColor(.primary) Spacer() } .padding(.vertical, 6) .padding(.horizontal, 12) .contentShape(Rectangle()) } .buttonStyle(.plain) .onHover { h in if h { NSCursor.pointingHand.push() } else { NSCursor.pop() } } // Collapsible saved chats if !sessionStore.sessions.isEmpty { DisclosureGroup(isExpanded: $chatsExpanded) { VStack(spacing: 2) { ForEach(sessionStore.sessions.prefix(15)) { session in Button(action: { sessionStore.selectSession(session.id) NotificationCenter.default.post(name: .switchToTab, object: nil, userInfo: ["tab": 0]) withAnimation { isVisible = false } }) { HStack(spacing: 8) { Image(systemName: session.id == sessionStore.currentSessionId ? "message.fill" : "message") .font(.system(size: 11)) .foregroundColor(session.id == sessionStore.currentSessionId ? Color(red: 0.83, green: 0.69, blue: 0.22) : .secondary) .frame(width: 16) Text(session.title) .font(.caption) .foregroundColor(session.id == sessionStore.currentSessionId ? .primary : .secondary) .lineLimit(1) Spacer() } .padding(.vertical, 4) .padding(.horizontal, 12) .background( RoundedRectangle(cornerRadius: 5) .fill(session.id == sessionStore.currentSessionId ? Color(red: 0.83, green: 0.69, blue: 0.22).opacity(0.1) : Color.clear) ) .contentShape(Rectangle()) } .buttonStyle(.plain) .onHover { h in if h { NSCursor.pointingHand.push() } else { NSCursor.pop() } } .contextMenu { Button(role: .destructive) { sessionStore.deleteSession(session.id) } label: { Label(lang.isRu ? "\u{0423}\u{0434}\u{0430}\u{043B}\u{0438}\u{0442}\u{044C}" : "Delete", systemImage: "trash") } } } } } label: { HStack(spacing: 6) { Image(systemName: "bubble.left.and.bubble.right") .font(.system(size: 11)) Text(lang.isRu ? "\u{0427}\u{0430}\u{0442}\u{044B} (\(sessionStore.sessions.count))" : "Chats (\(sessionStore.sessions.count))") .font(.caption) } .foregroundColor(.secondary) .padding(.horizontal, 12) } } } } // MARK: - Montana Section (only working features) private var montanaSection: some View { VStack(alignment: .leading, spacing: 12) { Text("Montana") .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) VStack(spacing: 4) { SidebarNavItem(icon: "banknote", label: lang.isRu ? "\u{041A}\u{043E}\u{0448}\u{0435}\u{043B}\u{0451}\u{043A}" : "Wallet", tag: 1, isVisible: $isVisible) SidebarNavItem(icon: "pentagon", label: lang.isRu ? "\u{0422}\u{0430}\u{0439}\u{043C}\u{0447}\u{0435}\u{0439}\u{043D}" : "TimeChain", tag: 8, isVisible: $isVisible) SidebarNavItem(icon: "clock.arrow.circlepath", label: lang.isRu ? "\u{0418}\u{0441}\u{0442}\u{043E}\u{0440}\u{0438}\u{044F}" : "History", tag: 7, isVisible: $isVisible) } } } // MARK: - Exchange Section private var exchangeSection: some View { VStack(alignment: .leading, spacing: 12) { Text(lang.isRu ? "\u{041E}\u{0431}\u{043C}\u{0435}\u{043D}" : "Exchange") .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) VStack(spacing: 4) { SidebarExchangeItem(icon: "bitcoinsign.circle.fill", label: "BTC \u{2192} \u{0248}") SidebarExchangeItem(icon: "dollarsign.circle.fill", label: "USD \u{2192} \u{0248}") SidebarExchangeItem(icon: "rublesign.circle.fill", label: "RUB \u{2192} \u{0248}") } } } // MARK: - Settings private var settingsSection: some View { SidebarNavItem(icon: "gear", label: lang.isRu ? "\u{041D}\u{0430}\u{0441}\u{0442}\u{0440}\u{043E}\u{0439}\u{043A}\u{0438}" : "Settings", tag: 9, isVisible: $isVisible) } // MARK: - Logout private var logoutSection: some View { Button(action: { showLogoutConfirm = true }) { HStack(spacing: 8) { Image(systemName: "rectangle.portrait.and.arrow.right") .font(.system(size: 14)) .foregroundColor(.red.opacity(0.8)) .frame(width: 20) Text(lang.isRu ? "\u{0412}\u{044B}\u{0445}\u{043E}\u{0434}" : "Log Out") .font(.callout) .foregroundColor(.red.opacity(0.8)) Spacer() } .padding(.vertical, 6) .padding(.horizontal, 12) .contentShape(Rectangle()) } .buttonStyle(.plain) .onHover { h in if h { NSCursor.pointingHand.push() } else { NSCursor.pop() } } } } // MARK: - Nav Item struct SidebarNavItem: View { let icon: String let label: String let tag: Int @Binding var isVisible: Bool var body: some View { Button(action: { NotificationCenter.default.post(name: .switchToTab, object: nil, userInfo: ["tab": tag]) withAnimation { isVisible = false } }) { HStack(spacing: 8) { Image(systemName: icon) .font(.system(size: 14)) .foregroundColor(Color(red: 0.0, green: 0.83, blue: 1.0)) .frame(width: 20) Text(label) .font(.callout) .foregroundColor(.primary) Spacer() } .padding(.vertical, 6) .padding(.horizontal, 12) .contentShape(Rectangle()) } .buttonStyle(.plain) .onHover { h in if h { NSCursor.pointingHand.push() } else { NSCursor.pop() } } } } // MARK: - Exchange Item struct SidebarExchangeItem: View { let icon: String let label: String @State private var showExchange = false var currency: String { if label.contains("BTC") { return "BTC" } if label.contains("USD") { return "USD" } if label.contains("RUB") { return "RUB" } return "USD" } var body: some View { Button(action: { showExchange = true }) { HStack(spacing: 8) { Image(systemName: icon) .font(.system(size: 14)) .foregroundColor(Color(red: 0.0, green: 0.83, blue: 1.0)) .frame(width: 20) Text(label) .font(.callout) .foregroundColor(.primary) Spacer() } .padding(.vertical, 6) .padding(.horizontal, 12) .contentShape(Rectangle()) } .buttonStyle(.plain) .onHover { h in if h { NSCursor.pointingHand.push() } else { NSCursor.pop() } } .popover(isPresented: $showExchange) { ExchangeCalculatorView(currency: currency) } } } // MARK: - Exchange Calculator struct ExchangeCalculatorView: View { let currency: String @ObservedObject private var lang = LanguageManager.shared @State private var amount: String = "" @State private var isReversed = false private let gold = Color(red: 0.85, green: 0.68, blue: 0.25) private let cyan = Color(red: 0.0, green: 0.83, blue: 1.0) private let rateUSD: Double = 0.1605 private let rateRUB: Double = 12.04 private let rateBTC: Double = 0.0000000165 private var rate: Double { switch currency { case "BTC": return rateBTC case "RUB": return rateRUB default: return rateUSD } } private var currencySymbol: String { switch currency { case "BTC": return "BTC" case "RUB": return "\u{20BD}" default: return "$" } } private var convertedAmount: String { guard let value = Double(amount), value > 0 else { return "0" } let result: Double if isReversed { result = value * rate } else { result = value / rate } if currency == "BTC" && !isReversed { return String(format: "%.0f", result) } if result < 0.01 { return String(format: "%.8f", result) } let formatter = NumberFormatter() formatter.numberStyle = .decimal formatter.maximumFractionDigits = 2 formatter.groupingSeparator = " " return formatter.string(from: NSNumber(value: result)) ?? "\(result)" } var body: some View { VStack(spacing: 16) { HStack { Image(systemName: "arrow.left.arrow.right.circle.fill") .foregroundColor(gold) Text(lang.isRu ? "\u{041A}\u{043E}\u{043D}\u{0432}\u{0435}\u{0440}\u{0442}\u{0435}\u{0440}" : "Converter") .font(.system(size: 14, weight: .bold)) Spacer() } Text("1 \u{0248} = \(rateDisplay)") .font(.system(size: 11)) .foregroundColor(.secondary) VStack(spacing: 8) { HStack { Text(isReversed ? "\u{0248}" : currencySymbol) .font(.system(size: 14, weight: .bold)) .foregroundColor(isReversed ? gold : cyan) .frame(width: 30) TextField("0", text: $amount) .textFieldStyle(.plain) .font(.system(size: 16, weight: .medium, design: .monospaced)) } .padding(10) .background(Color(NSColor.controlBackgroundColor)) .cornerRadius(8) Button(action: { isReversed.toggle() }) { Image(systemName: "arrow.up.arrow.down") .font(.system(size: 12)) .foregroundColor(gold) .padding(6) .background(gold.opacity(0.1)) .clipShape(Circle()) } .buttonStyle(.plain) HStack { Text(isReversed ? currencySymbol : "\u{0248}") .font(.system(size: 14, weight: .bold)) .foregroundColor(isReversed ? cyan : gold) .frame(width: 30) Text(convertedAmount) .font(.system(size: 16, weight: .bold, design: .monospaced)) .foregroundColor(.primary) Spacer() } .padding(10) .background(Color(NSColor.controlBackgroundColor)) .cornerRadius(8) } Text("Genesis Price (09.01.2026)") .font(.system(size: 9)) .foregroundColor(.secondary) } .padding(16) .frame(width: 260) } private var rateDisplay: String { switch currency { case "BTC": return String(format: "%.10f BTC", rateBTC) case "RUB": return String(format: "%.2f \u{20BD}", rateRUB) default: return String(format: "$%.4f", rateUSD) } } }