mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 11:45:19 +00:00
Optimization 5: Fix animation overhead
- Removed redundant animation modifiers (multiple animations on same view) - Replaced spring animations with simpler easeInOut/easeOut curves - Spring animations are computationally expensive, now using 0.2s easeInOut - Added scroll throttling to prevent excessive animations on message updates - Scroll animations limited to once per 0.5 seconds - Removed withAnimation wrapper from scrollTo calls - Simplified drag gesture animations from spring to easeOut This reduces CPU usage during UI transitions and message updates
This commit is contained in:
@@ -26,6 +26,8 @@ struct ContentView: View {
|
|||||||
@State private var selectedMessageSender: String?
|
@State private var selectedMessageSender: String?
|
||||||
@State private var selectedMessageSenderID: String?
|
@State private var selectedMessageSenderID: String?
|
||||||
@FocusState private var isNicknameFieldFocused: Bool
|
@FocusState private var isNicknameFieldFocused: Bool
|
||||||
|
@State private var lastScrollTime: Date = .distantPast
|
||||||
|
@State private var scrollThrottleTimer: Timer?
|
||||||
|
|
||||||
private var backgroundColor: Color {
|
private var backgroundColor: Color {
|
||||||
colorScheme == .dark ? Color.black : Color.white
|
colorScheme == .dark ? Color.black : Color.white
|
||||||
@@ -65,19 +67,18 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
if value.translation.width > 50 || (value.translation.width > 30 && value.velocity.width > 300) {
|
if value.translation.width > 50 || (value.translation.width > 30 && value.velocity.width > 300) {
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
showPrivateChat = false
|
showPrivateChat = false
|
||||||
backSwipeOffset = 0
|
backSwipeOffset = 0
|
||||||
viewModel.endPrivateChat()
|
viewModel.endPrivateChat()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeOut(duration: 0.15)) {
|
||||||
backSwipeOffset = 0
|
backSwipeOffset = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: showPrivateChat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sidebar overlay
|
// Sidebar overlay
|
||||||
@@ -86,7 +87,7 @@ struct ContentView: View {
|
|||||||
Color.clear
|
Color.clear
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showSidebar = false
|
showSidebar = false
|
||||||
sidebarDragOffset = 0
|
sidebarDragOffset = 0
|
||||||
}
|
}
|
||||||
@@ -101,15 +102,14 @@ struct ContentView: View {
|
|||||||
.transition(.move(edge: .trailing))
|
.transition(.move(edge: .trailing))
|
||||||
}
|
}
|
||||||
.offset(x: showSidebar ? -sidebarDragOffset : geometry.size.width - sidebarDragOffset)
|
.offset(x: showSidebar ? -sidebarDragOffset : geometry.size.width - sidebarDragOffset)
|
||||||
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: showSidebar)
|
.animation(.easeInOut(duration: 0.25), value: showSidebar)
|
||||||
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: sidebarDragOffset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.frame(minWidth: 600, minHeight: 400)
|
.frame(minWidth: 600, minHeight: 400)
|
||||||
#endif
|
#endif
|
||||||
.onChange(of: viewModel.selectedPrivateChatPeer) { newValue in
|
.onChange(of: viewModel.selectedPrivateChatPeer) { newValue in
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showPrivateChat = newValue != nil
|
showPrivateChat = newValue != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ struct ContentView: View {
|
|||||||
Button("private message") {
|
Button("private message") {
|
||||||
if let peerID = selectedMessageSenderID {
|
if let peerID = selectedMessageSenderID {
|
||||||
viewModel.startPrivateChat(with: peerID)
|
viewModel.startPrivateChat(with: peerID)
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showSidebar = false
|
showSidebar = false
|
||||||
sidebarDragOffset = 0
|
sidebarDragOffset = 0
|
||||||
}
|
}
|
||||||
@@ -242,10 +242,19 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.onChange(of: viewModel.messages.count) { _ in
|
.onChange(of: viewModel.messages.count) { _ in
|
||||||
if privatePeer == nil && !viewModel.messages.isEmpty {
|
if privatePeer == nil && !viewModel.messages.isEmpty {
|
||||||
withAnimation {
|
// Throttle scroll animations to prevent excessive UI updates
|
||||||
// Scroll to last message in windowed view
|
let now = Date()
|
||||||
let lastId = viewModel.messages.suffix(100).last?.id
|
if now.timeIntervalSince(lastScrollTime) > 0.5 {
|
||||||
proxy.scrollTo(lastId, anchor: .bottom)
|
// Immediate scroll if enough time has passed
|
||||||
|
lastScrollTime = now
|
||||||
|
proxy.scrollTo(viewModel.messages.suffix(100).last?.id, anchor: .bottom)
|
||||||
|
} else {
|
||||||
|
// Schedule a delayed scroll
|
||||||
|
scrollThrottleTimer?.invalidate()
|
||||||
|
scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
||||||
|
lastScrollTime = Date()
|
||||||
|
proxy.scrollTo(viewModel.messages.suffix(100).last?.id, anchor: .bottom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,10 +262,17 @@ struct ContentView: View {
|
|||||||
if let peerID = privatePeer,
|
if let peerID = privatePeer,
|
||||||
let messages = viewModel.privateChats[peerID],
|
let messages = viewModel.privateChats[peerID],
|
||||||
!messages.isEmpty {
|
!messages.isEmpty {
|
||||||
withAnimation {
|
// Same throttling for private chats
|
||||||
// Scroll to last message in windowed view
|
let now = Date()
|
||||||
let lastId = messages.suffix(100).last?.id
|
if now.timeIntervalSince(lastScrollTime) > 0.5 {
|
||||||
proxy.scrollTo(lastId, anchor: .bottom)
|
lastScrollTime = now
|
||||||
|
proxy.scrollTo(messages.suffix(100).last?.id, anchor: .bottom)
|
||||||
|
} else {
|
||||||
|
scrollThrottleTimer?.invalidate()
|
||||||
|
scrollThrottleTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
||||||
|
lastScrollTime = Date()
|
||||||
|
proxy.scrollTo(messages.suffix(100).last?.id, anchor: .bottom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,7 +616,7 @@ struct ContentView: View {
|
|||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if !isMe && peerNicknames[peerID] != nil {
|
if !isMe && peerNicknames[peerID] != nil {
|
||||||
viewModel.startPrivateChat(with: peerID)
|
viewModel.startPrivateChat(with: peerID)
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showSidebar = false
|
showSidebar = false
|
||||||
sidebarDragOffset = 0
|
sidebarDragOffset = 0
|
||||||
}
|
}
|
||||||
@@ -647,7 +663,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeOut(duration: 0.2)) {
|
||||||
if !showSidebar {
|
if !showSidebar {
|
||||||
if value.translation.width < -100 || (value.translation.width < -50 && value.velocity.width < -500) {
|
if value.translation.width < -100 || (value.translation.width < -50 && value.velocity.width < -500) {
|
||||||
showSidebar = true
|
showSidebar = true
|
||||||
@@ -749,7 +765,7 @@ struct ContentView: View {
|
|||||||
.foregroundColor(viewModel.isConnected ? textColor : Color.red)
|
.foregroundColor(viewModel.isConnected ? textColor : Color.red)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showSidebar.toggle()
|
showSidebar.toggle()
|
||||||
sidebarDragOffset = 0
|
sidebarDragOffset = 0
|
||||||
}
|
}
|
||||||
@@ -766,7 +782,7 @@ struct ContentView: View {
|
|||||||
let privatePeerNick = viewModel.meshService.getPeerNicknames()[privatePeerID] {
|
let privatePeerNick = viewModel.meshService.getPeerNicknames()[privatePeerID] {
|
||||||
HStack {
|
HStack {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
showPrivateChat = false
|
showPrivateChat = false
|
||||||
viewModel.endPrivateChat()
|
viewModel.endPrivateChat()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user