diff --git a/bitchat/BitchatApp.swift b/bitchat/BitchatApp.swift index bb74a12a..644554b0 100644 --- a/bitchat/BitchatApp.swift +++ b/bitchat/BitchatApp.swift @@ -53,9 +53,10 @@ struct BitchatApp: App { // Inject live Noise service into VerificationService to avoid creating new BLE instances VerificationService.shared.configure(with: chatViewModel.meshService.getNoiseService()) // Prewarm Nostr identity and QR to make first VERIFY sheet fast + let nickname = chatViewModel.nickname DispatchQueue.global(qos: .utility).async { let npub = try? idBridge.getCurrentNostrIdentity()?.npub - _ = VerificationService.shared.buildMyQRString(nickname: chatViewModel.nickname, npub: npub) + _ = VerificationService.shared.buildMyQRString(nickname: nickname, npub: npub) } appDelegate.chatViewModel = chatViewModel diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 1a5ee44e..544befc7 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -465,17 +465,17 @@ struct ContentView: View { } else { // Schedule a delayed scroll scrollThrottleTimer?.invalidate() - scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: TransportConfig.uiScrollThrottleSeconds, repeats: false) { _ in - lastScrollTime = Date() - let contextKey: String = { - switch locationManager.selectedChannel { - case .mesh: return "mesh" - case .location(let ch): return "geo:\(ch.geohash)" - } - }() - let count = windowCountPublic - let target = viewModel.messages.suffix(count).last.map { "\(contextKey)|\($0.id)" } - DispatchQueue.main.async { + scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: TransportConfig.uiScrollThrottleSeconds, repeats: false) { [weak viewModel] _ in + Task { @MainActor in + lastScrollTime = Date() + let contextKey: String = { + switch locationManager.selectedChannel { + case .mesh: return "mesh" + case .location(let ch): return "geo:\(ch.geohash)" + } + }() + let count = windowCountPublic + let target = viewModel?.messages.suffix(count).last.map { "\(contextKey)|\($0.id)" } if let target = target { proxy.scrollTo(target, anchor: .bottom) } } } @@ -643,9 +643,11 @@ struct ContentView: View { .frame(maxWidth: .infinity, alignment: .leading) .onChange(of: messageText) { newValue in autocompleteDebounceTimer?.invalidate() - autocompleteDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { _ in + autocompleteDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.15, repeats: false) { [weak viewModel] _ in let cursorPosition = newValue.count - viewModel.updateAutocomplete(for: newValue, cursorPosition: cursorPosition) + Task { @MainActor in + viewModel?.updateAutocomplete(for: newValue, cursorPosition: cursorPosition) + } } }