mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
ChannelMessageList and UserMessageList: debouncedScrollToBottom; keyboardWillShowNotification/keyboardDidShowNotification
This commit is contained in:
parent
bce26c46de
commit
ee1a7c4415
2 changed files with 69 additions and 18 deletions
|
|
@ -23,7 +23,8 @@ struct ChannelMessageList: View {
|
|||
@AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1
|
||||
@State private var messageToHighlight: Int64 = 0
|
||||
@FetchRequest private var allPrivateMessages: FetchedResults<MessageEntity>
|
||||
|
||||
@State private var scrollToBottomWorkItem: DispatchWorkItem?
|
||||
|
||||
init(myInfo: MyInfoEntity, channel: ChannelEntity) {
|
||||
self.myInfo = myInfo
|
||||
self.channel = channel
|
||||
|
|
@ -50,15 +51,30 @@ struct ChannelMessageList: View {
|
|||
for unreadMessage in allPrivateMessages.filter({ !$0.read }) {
|
||||
unreadMessage.read = true
|
||||
}
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] All unread messages marked as read.")
|
||||
|
||||
if context.hasChanges {
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] All unread messages marked as read.")
|
||||
}
|
||||
|
||||
appState.unreadChannelMessages = myInfo.unreadMessages
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
} catch {
|
||||
Logger.data.error("Failed to read messages: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func debouncedScrollToBottom(scrollView: ScrollViewProxy, lastMessageId: Int64?, delay: TimeInterval = 0.1) {
|
||||
scrollToBottomWorkItem?.cancel()
|
||||
|
||||
let scrollTarget: AnyHashable = lastMessageId != nil ? lastMessageId : "bottomAnchor"
|
||||
let work = DispatchWorkItem {
|
||||
scrollView.scrollTo(scrollTarget, anchor: .bottom)
|
||||
}
|
||||
scrollToBottomWorkItem = work
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: work)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// Cast allPrivateMessages to an array for easier indexing and ForEach.
|
||||
let messages: [MessageEntity] = Array(allPrivateMessages)
|
||||
|
|
@ -71,6 +87,8 @@ struct ChannelMessageList: View {
|
|||
return dict
|
||||
}()
|
||||
|
||||
let lastMessageId: Int64? = messages.last?.messageId
|
||||
|
||||
ScrollViewReader { scrollView in
|
||||
ScrollView {
|
||||
LazyVStack {
|
||||
|
|
@ -98,11 +116,10 @@ struct ChannelMessageList: View {
|
|||
// So, run it in the main queue after everything saves and stabilizes
|
||||
DispatchQueue.main.async {
|
||||
markMessagesAsRead()
|
||||
scrollView.scrollTo("bottomAnchor", anchor: .bottom)
|
||||
}
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Color.clear
|
||||
.frame(height: 1)
|
||||
|
|
@ -113,12 +130,21 @@ struct ChannelMessageList: View {
|
|||
.defaultScrollAnchorTopAlignment()
|
||||
.defaultScrollAnchorBottomSizeChanges()
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
|
||||
// Keyboard is about to appear: keyboard show animation hasn't quite started yet.
|
||||
// Schedule an immediate scroll to the bottom message by its messageId, in order to force LazyVStack to render that cell if it isn't rendered already
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.0)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
|
||||
// Keyboard is fully visible.
|
||||
// Scroll after the keyboard is fully showing, with a short delay to allow things to settle (TextMessageField height update, for example)
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.1)
|
||||
}
|
||||
.onChange(of: messageFieldFocused) {
|
||||
if messageFieldFocused {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
scrollView.scrollTo("bottomAnchor", anchor: .bottom)
|
||||
}
|
||||
}
|
||||
// macOS doesn't have keyboard show animation, but we still want to scroll to the bottom.
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.0)
|
||||
}
|
||||
}
|
||||
TextMessageField(
|
||||
destination: .channel(channel),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ struct UserMessageList: View {
|
|||
@State private var redrawTapbacksTrigger = UUID()
|
||||
@AppStorage("preferredPeripheralNum") private var preferredPeripheralNum = -1
|
||||
@FetchRequest private var allPrivateMessages: FetchedResults<MessageEntity>
|
||||
@State private var scrollToBottomWorkItem: DispatchWorkItem?
|
||||
|
||||
init(user: UserEntity) {
|
||||
self.user = user
|
||||
|
|
@ -41,8 +42,11 @@ struct UserMessageList: View {
|
|||
for unreadMessage in allPrivateMessages.filter({ !$0.read }) {
|
||||
unreadMessage.read = true
|
||||
}
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] All unread direct messages marked as read for user \(user.num, privacy: .public).")
|
||||
|
||||
if context.hasChanges {
|
||||
try context.save()
|
||||
Logger.data.info("📖 [App] All unread direct messages marked as read for user \(user.num, privacy: .public).")
|
||||
}
|
||||
|
||||
if let connectedPeripheralNum = accessoryManager.activeDeviceNum,
|
||||
let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: context),
|
||||
|
|
@ -56,6 +60,17 @@ struct UserMessageList: View {
|
|||
}
|
||||
}
|
||||
|
||||
func debouncedScrollToBottom(scrollView: ScrollViewProxy, lastMessageId: Int64?, delay: TimeInterval = 0.1) {
|
||||
scrollToBottomWorkItem?.cancel()
|
||||
|
||||
let scrollTarget: AnyHashable = lastMessageId != nil ? lastMessageId : "bottomAnchor"
|
||||
let work = DispatchWorkItem {
|
||||
scrollView.scrollTo(scrollTarget, anchor: .bottom)
|
||||
}
|
||||
scrollToBottomWorkItem = work
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: work)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// Cast user.messageList to an array for easier indexing and ForEach.
|
||||
let messages: [MessageEntity] = Array(allPrivateMessages)
|
||||
|
|
@ -68,6 +83,8 @@ struct UserMessageList: View {
|
|||
return dict
|
||||
}()
|
||||
|
||||
let lastMessageId: Int64? = messages.last?.messageId
|
||||
|
||||
VStack {
|
||||
ScrollViewReader { scrollView in
|
||||
ScrollView {
|
||||
|
|
@ -96,11 +113,10 @@ struct UserMessageList: View {
|
|||
// So, run it in the main queue after everything saves and stabilizes
|
||||
DispatchQueue.main.async {
|
||||
markMessagesAsRead()
|
||||
scrollView.scrollTo("bottomAnchor", anchor: .bottom)
|
||||
}
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// Invisible spacer to detect reaching bottom
|
||||
Color.clear
|
||||
|
|
@ -112,12 +128,21 @@ struct UserMessageList: View {
|
|||
.defaultScrollAnchorTopAlignment()
|
||||
.defaultScrollAnchorBottomSizeChanges()
|
||||
.scrollDismissesKeyboard(.immediately)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
|
||||
// Keyboard is about to appear: keyboard show animation hasn't quite started yet.
|
||||
// Schedule an immediate scroll to the bottom message by its messageId, in order to force LazyVStack to render that cell if it isn't rendered already
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.0)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
|
||||
// Keyboard is fully visible.
|
||||
// Scroll after the keyboard is fully showing, with a short delay to allow things to settle (TextMessageField height update, for example)
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.1)
|
||||
}
|
||||
.onChange(of: messageFieldFocused) {
|
||||
if messageFieldFocused {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
scrollView.scrollTo("bottomAnchor", anchor: .bottom)
|
||||
}
|
||||
}
|
||||
// macOS doesn't have keyboard show animation, but we still want to scroll to the bottom.
|
||||
debouncedScrollToBottom(scrollView: scrollView, lastMessageId: lastMessageId, delay: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
TextMessageField(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue