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
This commit is contained in:
jack
2025-07-04 02:21:59 +02:00
parent c7baf31f03
commit cce43fcfc7
7 changed files with 585 additions and 17 deletions
+148
View File
@@ -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
+10 -7
View File
@@ -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 = "<group>"; };
3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = "<group>"; };
763E0DBA9492A654FC0CDCB9 /* AppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppInfoView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = "<group>"; };
@@ -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;
+8 -1
View File
@@ -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
}
}
+108 -8
View File
@@ -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()
}
}
}
+303
View File
@@ -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()
}
+8
View File
@@ -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("@")
-1
View File
@@ -9,7 +9,6 @@ options:
settings:
MARKETING_VERSION: 1.0.0
CURRENT_PROJECT_VERSION: 1
DEVELOPMENT_TEAM: "L3N5LHJD5Y"
targets:
bitchat: