From cce43fcfc7a8428ec6556a833e48375f5c29418e Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 4 Jul 2025 02:21:59 +0200 Subject: [PATCH] Add interactive logo with info/panic modes and battery optimization - Single tap logo shows comprehensive app info and features - Triple tap logo triggers panic mode (instant data wipe) - Implement adaptive Bluetooth scanning based on battery level - Remove hardcoded development team ID for security - Fix store-and-forward description (12h for all, indefinite for favorites) - Add technical debt remediation plan - Fix macOS compatibility issues with navigation --- TECH_DEBT_PLAN.md | 148 ++++++++++ bitchat.xcodeproj/project.pbxproj | 17 +- bitchat/Protocols/BitchatProtocol.swift | 9 +- bitchat/Services/BluetoothMeshService.swift | 116 +++++++- bitchat/Views/AppInfoView.swift | 303 ++++++++++++++++++++ bitchat/Views/ContentView.swift | 8 + project.yml | 1 - 7 files changed, 585 insertions(+), 17 deletions(-) create mode 100644 TECH_DEBT_PLAN.md create mode 100644 bitchat/Views/AppInfoView.swift diff --git a/TECH_DEBT_PLAN.md b/TECH_DEBT_PLAN.md new file mode 100644 index 00000000..c876d313 --- /dev/null +++ b/TECH_DEBT_PLAN.md @@ -0,0 +1,148 @@ +# Bitchat Technical Debt Remediation Plan + +## Overview +This document outlines the technical debt in the bitchat project and provides a prioritized plan for addressing it. + +## Priority 1: Critical Security & Stability (1-2 weeks) + +### 1.1 Thread Safety Issues +**Problem**: Potential race conditions in message processing and peer management +**Solution**: +- Audit all concurrent access to shared state +- Add proper synchronization to message queues +- Use actor pattern for BluetoothMeshService +- Add thread sanitizer to debug builds + +### 1.2 Error Handling +**Problem**: Many errors are silently logged without proper recovery +**Solution**: +- Implement proper error propagation +- Add user-visible error states +- Create recovery mechanisms for common failures +- Add crash reporting for TestFlight + +### 1.3 Memory Management +**Problem**: Fragment cleanup could leak memory, no proper cleanup on errors +**Solution**: +- Add fragment timeout cleanup +- Implement proper weak references in closures +- Add memory pressure handling +- Profile with Instruments + +## Priority 2: Code Quality & Maintainability (2-3 weeks) + +### 2.1 Service Refactoring +**Problem**: BluetoothMeshService is 1600+ lines, doing too much +**Solution**: +- Extract message handling into MessageProcessor +- Extract peer management into PeerManager +- Extract fragment handling into FragmentManager +- Create proper dependency injection + +### 2.2 Testing Infrastructure +**Problem**: No unit tests, making refactoring risky +**Solution**: +- Add XCTest target +- Create mock implementations for Bluetooth +- Test encryption service thoroughly +- Add integration tests for message flow +- Aim for 70% code coverage + +### 2.3 Documentation +**Problem**: Limited code comments, no architecture docs +**Solution**: +- Add comprehensive header comments +- Document protocol specifications +- Create architecture diagrams +- Add inline documentation for complex logic + +## Priority 3: Performance & UX (3-4 weeks) + +### 3.1 Message Deduplication +**Problem**: Simple Set-based deduplication can grow unbounded +**Solution**: +- Implement LRU cache for message IDs +- Add time-based expiration +- Consider bloom filters for efficiency + +### 3.2 Connection Management +**Problem**: No connection pooling or retry logic +**Solution**: +- Implement connection state machine +- Add exponential backoff for retries +- Pool peripheral connections +- Add connection quality metrics + +### 3.3 UI Responsiveness +**Problem**: Heavy operations on main thread +**Solution**: +- Move encryption to background queues +- Add loading states for operations +- Implement message pagination +- Add pull-to-refresh + +## Priority 4: Feature Enhancements (4-6 weeks) + +### 4.1 Message Persistence (Optional) +**Problem**: All messages ephemeral, no history +**Solution**: +- Add encrypted SQLite storage +- Implement configurable retention +- Add search functionality +- Maintain privacy-first approach + +### 4.2 Protocol Improvements +**Problem**: No versioning, hard to upgrade +**Solution**: +- Add protocol version negotiation +- Implement capability discovery +- Plan migration strategy +- Add feature flags + +### 4.3 Network Visualization +**Problem**: Users don't understand mesh topology +**Solution**: +- Add network graph view +- Show message routing paths +- Display peer connection quality +- Add statistics dashboard + +## Implementation Strategy + +### Phase 1: Foundation (Weeks 1-2) +1. Set up testing infrastructure +2. Add thread sanitizer and memory profiler +3. Begin service refactoring +4. Fix critical thread safety issues + +### Phase 2: Stability (Weeks 3-4) +1. Complete service refactoring +2. Add comprehensive error handling +3. Implement proper memory management +4. Add initial test coverage + +### Phase 3: Quality (Weeks 5-6) +1. Add documentation +2. Improve connection management +3. Optimize performance bottlenecks +4. Enhance UI responsiveness + +### Phase 4: Enhancement (Weeks 7-10) +1. Add optional persistence +2. Implement protocol versioning +3. Build network visualization +4. Polish for production + +## Success Metrics +- Zero crashes in TestFlight +- 70% test coverage +- Sub-100ms message processing +- < 5% battery drain per hour +- Clean architecture with < 300 lines per class + +## Risk Mitigation +- Keep all changes backwards compatible initially +- Add feature flags for risky changes +- Extensive TestFlight testing between phases +- Maintain current security properties +- Regular security audits of changes \ No newline at end of file diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index 27aab305..fdf73b9b 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 63; objects = { /* Begin PBXBuildFile section */ @@ -20,6 +20,8 @@ 7DD72D928FF9DD3CA81B46B0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */; }; 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; }; 92D34E7A07C990C8A815B0CE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; + ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */; }; + AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */; }; BCCFEDC1EBE59323C3C470BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */; }; D450CF41F207BDE1A1AAA56E /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; }; D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; }; @@ -32,8 +34,9 @@ 3448F84BF86A42A3CC4A9379 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = ""; }; + 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = ""; }; 7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = ""; }; D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = ""; }; @@ -85,6 +88,7 @@ A55126E93155456CAA8D6656 /* Views */ = { isa = PBXGroup; children = ( + 763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */, A08E03AA0C63E97C91749AEC /* ContentView.swift */, ); path = Views; @@ -158,11 +162,9 @@ LastUpgradeCheck = 1430; TargetAttributes = { 0576A29205865664C0937536 = { - DevelopmentTeam = L3N5LHJD5Y; ProvisioningStyle = Automatic; }; AF077EA0474EDEDE2C72716C = { - DevelopmentTeam = L3N5LHJD5Y; ProvisioningStyle = Automatic; }; }; @@ -177,7 +179,6 @@ ); mainGroup = 18198ED912AAF495D8AF7763; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 54; projectDirPath = ""; projectRoot = ""; targets = ( @@ -211,6 +212,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */, 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */, 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */, 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */, @@ -226,6 +228,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */, F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */, 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */, 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */, @@ -249,6 +252,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -275,6 +279,7 @@ CODE_SIGNING_REQUIRED = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -353,7 +358,6 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -437,7 +441,6 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index 049a9b7c..8314e81b 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -94,5 +94,12 @@ protocol BitchatDelegate: AnyObject { func didUpdatePeerList(_ peers: [String]) // Optional method to check if a fingerprint belongs to a favorite peer - @objc optional func isFavorite(fingerprint: String) -> Bool + func isFavorite(fingerprint: String) -> Bool +} + +// Provide default implementation to make it effectively optional +extension BitchatDelegate { + func isFavorite(fingerprint: String) -> Bool { + return false + } } \ No newline at end of file diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index c640d5f7..61666aac 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -4,6 +4,7 @@ import Combine import CryptoKit #if os(macOS) import AppKit +import IOKit.ps #else import UIKit #endif @@ -58,9 +59,11 @@ class BluetoothMeshService: NSObject { // Battery and range optimizations private var scanDutyCycleTimer: Timer? private var isActivelyScanning = true - private let activeScanDuration: TimeInterval = 2.0 // Scan actively for 2 seconds - private let scanPauseDuration: TimeInterval = 3.0 // Pause for 3 seconds + private var activeScanDuration: TimeInterval = 2.0 // Scan actively for 2 seconds - will be adjusted based on battery + private var scanPauseDuration: TimeInterval = 3.0 // Pause for 3 seconds - will be adjusted based on battery private var lastRSSIUpdate: [String: Date] = [:] // Throttle RSSI updates + private var batteryMonitorTimer: Timer? + private var currentBatteryLevel: Float = 1.0 // Default to full battery // Fragment handling private var incomingFragments: [String: [Int: Data]] = [:] // fragmentID -> [index: data] @@ -226,6 +229,9 @@ class BluetoothMeshService: NSObject { options: scanOptions ) + // Update scan parameters based on battery before starting + updateScanParametersForBattery() + // Implement scan duty cycling for battery efficiency scheduleScanDutyCycle() } @@ -524,7 +530,7 @@ class BluetoothMeshService: NSObject { // Check if recipient is a favorite via their public key fingerprint if let publicKeyData = self.encryptionService.getPeerIdentityKey(recipientPeerID) { let fingerprint = self.getPublicKeyFingerprint(publicKeyData) - isForFavorite = self.delegate?.isFavorite?(fingerprint: fingerprint) ?? false + isForFavorite = self.delegate?.isFavorite(fingerprint: fingerprint) ?? false } } @@ -612,10 +618,17 @@ class BluetoothMeshService: NSObject { peripheral.state == .connected else { return } // Create a new packet with fresh timestamp - var packet = storedMessage.packet - packet.timestamp = UInt64(Date().timeIntervalSince1970) + let updatedPacket = BitchatPacket( + type: storedMessage.packet.type, + senderID: storedMessage.packet.senderID, + recipientID: storedMessage.packet.recipientID, + timestamp: UInt64(Date().timeIntervalSince1970), + payload: storedMessage.packet.payload, + signature: storedMessage.packet.signature, + ttl: storedMessage.packet.ttl + ) - if let data = packet.toBinaryData() { + if let data = updatedPacket.toBinaryData() { peripheral.writeValue(data, for: characteristic, type: .withoutResponse) print("[CACHE] Sent cached message \(index + 1)/\(messagesToSend.count) to \(peerID)") } @@ -882,7 +895,7 @@ class BluetoothMeshService: NSObject { // Check if this is a favorite using their public key fingerprint if let publicKeyData = self.encryptionService.getPeerIdentityKey(senderID) { let fingerprint = self.getPublicKeyFingerprint(publicKeyData) - if self.delegate?.isFavorite?(fingerprint: fingerprint) ?? false { + if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false { NotificationService.shared.sendFavoriteOnlineNotification(nickname: nickname) // Send any cached messages for this favorite @@ -1014,7 +1027,7 @@ class BluetoothMeshService: NSObject { // Check if this message is for an offline favorite and cache it if let publicKeyData = self.encryptionService.getPeerIdentityKey(recipientIDString) { let fingerprint = self.getPublicKeyFingerprint(publicKeyData) - if self.delegate?.isFavorite?(fingerprint: fingerprint) ?? false { + if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false { // This is for a favorite peer - cache it even if they're offline print("[CACHE] Caching relayed message for offline favorite: \(recipientIDString)") self.cacheMessage(relayPacket, messageID: messageID) @@ -1615,4 +1628,91 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { startAdvertising() } } + + // MARK: - Battery Monitoring + + private func startBatteryMonitoring() { + // Update battery level immediately + updateBatteryLevel() + + // Monitor battery level every 30 seconds + batteryMonitorTimer = Timer.scheduledTimer(withTimeInterval: 30.0, repeats: true) { [weak self] _ in + self?.updateBatteryLevel() + } + } + + private func updateBatteryLevel() { + #if os(iOS) + UIDevice.current.isBatteryMonitoringEnabled = true + currentBatteryLevel = UIDevice.current.batteryLevel + + // Battery level is -1 when unknown (e.g., in simulator) + if currentBatteryLevel < 0 { + currentBatteryLevel = 1.0 // Assume full battery when unknown + } + #else + // macOS battery monitoring + if let batteryInfo = getMacOSBatteryInfo() { + currentBatteryLevel = batteryInfo + } else { + currentBatteryLevel = 1.0 // Assume full battery when unknown + } + #endif + + print("[BATTERY] Current battery level: \(Int(currentBatteryLevel * 100))%") + updateScanParametersForBattery() + } + + #if os(macOS) + private func getMacOSBatteryInfo() -> Float? { + let snapshot = IOPSCopyPowerSourcesInfo().takeRetainedValue() + let sources = IOPSCopyPowerSourcesList(snapshot).takeRetainedValue() as Array + + for source in sources { + if let description = IOPSGetPowerSourceDescription(snapshot, source).takeUnretainedValue() as? [String: Any] { + if let currentCapacity = description[kIOPSCurrentCapacityKey] as? Int, + let maxCapacity = description[kIOPSMaxCapacityKey] as? Int { + return Float(currentCapacity) / Float(maxCapacity) + } + } + } + return nil + } + #endif + + private func updateScanParametersForBattery() { + // Adaptive scanning based on battery level + // High battery (80%+): Normal scanning + // Medium battery (40-80%): Moderate power saving + // Low battery (20-40%): Aggressive power saving + // Critical battery (<20%): Maximum power saving + + if currentBatteryLevel > 0.8 { + // High battery: Normal operation + activeScanDuration = 2.0 + scanPauseDuration = 3.0 + print("[BATTERY] High battery mode: normal scanning") + } else if currentBatteryLevel > 0.4 { + // Medium battery: Moderate power saving + activeScanDuration = 1.5 + scanPauseDuration = 4.5 + print("[BATTERY] Medium battery mode: moderate power saving") + } else if currentBatteryLevel > 0.2 { + // Low battery: Aggressive power saving + activeScanDuration = 1.0 + scanPauseDuration = 8.0 + print("[BATTERY] Low battery mode: aggressive power saving") + } else { + // Critical battery: Maximum power saving + activeScanDuration = 0.5 + scanPauseDuration = 15.0 + print("[BATTERY] Critical battery mode: maximum power saving") + } + + // If we're currently in a duty cycle, restart it with new parameters + if scanDutyCycleTimer != nil { + scanDutyCycleTimer?.invalidate() + scheduleScanDutyCycle() + } + } } \ No newline at end of file diff --git a/bitchat/Views/AppInfoView.swift b/bitchat/Views/AppInfoView.swift new file mode 100644 index 00000000..674cead6 --- /dev/null +++ b/bitchat/Views/AppInfoView.swift @@ -0,0 +1,303 @@ +import SwiftUI + +struct AppInfoView: View { + @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme + + private var backgroundColor: Color { + colorScheme == .dark ? Color.black : Color.white + } + + private var textColor: Color { + colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + } + + private var secondaryTextColor: Color { + colorScheme == .dark ? Color.green.opacity(0.8) : Color(red: 0, green: 0.5, blue: 0).opacity(0.8) + } + + var body: some View { + #if os(macOS) + VStack(spacing: 0) { + // Custom header for macOS + HStack { + Spacer() + Button("Done") { + dismiss() + } + .buttonStyle(.plain) + .foregroundColor(textColor) + .padding() + } + .background(backgroundColor.opacity(0.95)) + + ScrollView { + VStack(alignment: .leading, spacing: 24) { + // Header + VStack(alignment: .center, spacing: 8) { + Text("bitchat*") + .font(.system(size: 32, weight: .bold, design: .monospaced)) + .foregroundColor(textColor) + + Text("secure mesh chat") + .font(.system(size: 16, design: .monospaced)) + .foregroundColor(secondaryTextColor) + } + .frame(maxWidth: .infinity) + .padding(.vertical) + + // Features + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Features") + + FeatureRow(icon: "wifi.slash", title: "Offline Communication", + description: "Works without internet using Bluetooth mesh networking") + + FeatureRow(icon: "lock.shield", title: "End-to-End Encryption", + description: "All messages encrypted with Curve25519 + AES-GCM") + + FeatureRow(icon: "antenna.radiowaves.left.and.right", title: "Extended Range", + description: "Messages relay through peers, reaching 300m+") + + FeatureRow(icon: "clock.arrow.circlepath", title: "Ephemeral Messages", + description: "Messages auto-delete after 5 minutes") + + FeatureRow(icon: "star.fill", title: "Favorites System", + description: "Store-and-forward messages for favorites indefinitely") + + FeatureRow(icon: "at", title: "Mentions", + description: "Use @nickname to notify specific users") + } + + // Privacy + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Privacy") + + FeatureRow(icon: "eye.slash", title: "No Tracking", + description: "No servers, accounts, or data collection") + + FeatureRow(icon: "shuffle", title: "Ephemeral Identity", + description: "New peer ID generated each session") + + FeatureRow(icon: "hand.raised.fill", title: "Panic Mode", + description: "Triple-tap logo to instantly clear all data") + } + + // How to Use + VStack(alignment: .leading, spacing: 16) { + SectionHeader("How to Use") + + VStack(alignment: .leading, spacing: 8) { + Text("• Set your nickname in the header") + Text("• Tap the people counter to see connected peers") + Text("• Tap a peer to start a private chat") + Text("• Use @nickname to mention someone") + Text("• Triple-tap the logo for panic mode") + } + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(textColor) + } + + // Technical Details + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Technical Details") + + VStack(alignment: .leading, spacing: 8) { + Text("Protocol: Custom binary over BLE") + Text("Encryption: Curve25519 + AES-256-GCM") + Text("Range: ~100m direct, 300m+ with relay") + Text("Store & Forward: 12h for all, ∞ for favorites") + Text("Battery: Adaptive scanning based on level") + Text("Platform: Universal (iOS, iPadOS, macOS)") + } + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(textColor) + } + + // Version + HStack { + Spacer() + Text("Version 1.0.0") + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(secondaryTextColor) + Spacer() + } + .padding(.top) + } + .padding() + } + .background(backgroundColor) + } + .frame(width: 600, height: 700) + #else + NavigationView { + ScrollView { + VStack(alignment: .leading, spacing: 24) { + // Header + VStack(alignment: .center, spacing: 8) { + Text("bitchat*") + .font(.system(size: 32, weight: .bold, design: .monospaced)) + .foregroundColor(textColor) + + Text("secure mesh chat") + .font(.system(size: 16, design: .monospaced)) + .foregroundColor(secondaryTextColor) + } + .frame(maxWidth: .infinity) + .padding(.vertical) + + // Features + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Features") + + FeatureRow(icon: "wifi.slash", title: "Offline Communication", + description: "Works without internet using Bluetooth mesh networking") + + FeatureRow(icon: "lock.shield", title: "End-to-End Encryption", + description: "All messages encrypted with Curve25519 + AES-GCM") + + FeatureRow(icon: "antenna.radiowaves.left.and.right", title: "Extended Range", + description: "Messages relay through peers, reaching 300m+") + + FeatureRow(icon: "clock.arrow.circlepath", title: "Ephemeral Messages", + description: "Messages auto-delete after 5 minutes") + + FeatureRow(icon: "star.fill", title: "Favorites System", + description: "Store-and-forward messages for favorites indefinitely") + + FeatureRow(icon: "at", title: "Mentions", + description: "Use @nickname to notify specific users") + } + + // Privacy + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Privacy") + + FeatureRow(icon: "eye.slash", title: "No Tracking", + description: "No servers, accounts, or data collection") + + FeatureRow(icon: "shuffle", title: "Ephemeral Identity", + description: "New peer ID generated each session") + + FeatureRow(icon: "hand.raised.fill", title: "Panic Mode", + description: "Triple-tap logo to instantly clear all data") + } + + // How to Use + VStack(alignment: .leading, spacing: 16) { + SectionHeader("How to Use") + + VStack(alignment: .leading, spacing: 8) { + Text("• Set your nickname in the header") + Text("• Tap the people counter to see connected peers") + Text("• Tap a peer to start a private chat") + Text("• Use @nickname to mention someone") + Text("• Triple-tap the logo for panic mode") + } + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(textColor) + } + + // Technical Details + VStack(alignment: .leading, spacing: 16) { + SectionHeader("Technical Details") + + VStack(alignment: .leading, spacing: 8) { + Text("Protocol: Custom binary over BLE") + Text("Encryption: Curve25519 + AES-256-GCM") + Text("Range: ~100m direct, 300m+ with relay") + Text("Store & Forward: 12h for all, ∞ for favorites") + Text("Battery: Adaptive scanning based on level") + Text("Platform: Universal (iOS, iPadOS, macOS)") + } + .font(.system(size: 14, design: .monospaced)) + .foregroundColor(textColor) + } + + // Version + HStack { + Spacer() + Text("Version 1.0.0") + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(secondaryTextColor) + Spacer() + } + .padding(.top) + } + .padding() + } + .background(backgroundColor) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + .foregroundColor(textColor) + } + } + } + #endif + } +} + +struct SectionHeader: View { + let title: String + @Environment(\.colorScheme) var colorScheme + + private var textColor: Color { + colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + } + + init(_ title: String) { + self.title = title + } + + var body: some View { + Text(title.uppercased()) + .font(.system(size: 16, weight: .bold, design: .monospaced)) + .foregroundColor(textColor) + .padding(.top, 8) + } +} + +struct FeatureRow: View { + let icon: String + let title: String + let description: String + @Environment(\.colorScheme) var colorScheme + + private var textColor: Color { + colorScheme == .dark ? Color.green : Color(red: 0, green: 0.5, blue: 0) + } + + private var secondaryTextColor: Color { + colorScheme == .dark ? Color.green.opacity(0.8) : Color(red: 0, green: 0.5, blue: 0).opacity(0.8) + } + + var body: some View { + HStack(alignment: .top, spacing: 12) { + Image(systemName: icon) + .font(.system(size: 20)) + .foregroundColor(textColor) + .frame(width: 30) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.system(size: 14, weight: .semibold, design: .monospaced)) + .foregroundColor(textColor) + + Text(description) + .font(.system(size: 12, design: .monospaced)) + .foregroundColor(secondaryTextColor) + .fixedSize(horizontal: false, vertical: true) + } + + Spacer() + } + } +} + +#Preview { + AppInfoView() +} \ No newline at end of file diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 8afe4c0b..92ad7b69 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -9,6 +9,7 @@ struct ContentView: View { @State private var showPeerList = false @State private var showSidebar = false @State private var sidebarDragOffset: CGFloat = 0 + @State private var showAppInfo = false private var backgroundColor: Color { colorScheme == .dark ? Color.black : Color.white @@ -132,6 +133,9 @@ struct ContentView: View { #if os(macOS) .frame(minWidth: 600, minHeight: 400) #endif + .sheet(isPresented: $showAppInfo) { + AppInfoView() + } } private var headerView: some View { @@ -180,6 +184,10 @@ struct ContentView: View { // PANIC: Triple-tap to clear all data viewModel.panicClearAllData() } + .onTapGesture(count: 1) { + // Single tap for app info + showAppInfo = true + } HStack(spacing: 0) { Text("@") diff --git a/project.yml b/project.yml index b4025ff9..9fcf827f 100644 --- a/project.yml +++ b/project.yml @@ -9,7 +9,6 @@ options: settings: MARKETING_VERSION: 1.0.0 CURRENT_PROJECT_VERSION: 1 - DEVELOPMENT_TEAM: "L3N5LHJD5Y" targets: bitchat: