mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Refactor messages navigation handling for NavigationSplitView
This commit is contained in:
parent
fbf059be6a
commit
86d362bb17
4 changed files with 103 additions and 74 deletions
|
|
@ -1,3 +1,4 @@
|
|||
import Combine
|
||||
import CoreData
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
|
|
@ -8,10 +9,16 @@ class Router: ObservableObject {
|
|||
@Published
|
||||
var navigationState: NavigationState
|
||||
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
init(
|
||||
navigationState: NavigationState = .bluetooth
|
||||
) {
|
||||
self.navigationState = navigationState
|
||||
|
||||
$navigationState.sink { destination in
|
||||
Logger.services.info("Routed to \(String(describing: destination))")
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func route(to destination: NavigationState) {
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ struct ChannelList: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@State var node: NodeInfoEntity?
|
||||
@Binding
|
||||
var node: NodeInfoEntity?
|
||||
|
||||
@Binding
|
||||
var channelSelection: ChannelEntity?
|
||||
|
||||
@State private var channelSelection: ChannelEntity? // Nothing selected by default.
|
||||
@State private var isPresentingDeleteChannelMessagesConfirm: Bool = false
|
||||
|
||||
@State private var isPresentingTraceRouteSentAlert = false
|
||||
|
|
@ -24,14 +27,14 @@ struct ChannelList: View {
|
|||
var restrictedChannels = ["gpio", "mqtt", "serial"]
|
||||
|
||||
@ViewBuilder
|
||||
private func makeNavigationLink(
|
||||
private func makeChannelRow(
|
||||
myInfo: MyInfoEntity,
|
||||
channel: ChannelEntity
|
||||
) -> some View {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
|
||||
|
||||
NavigationLink(destination: ChannelMessageList(myInfo: myInfo, channel: channel)) {
|
||||
NavigationLink(value: channel) {
|
||||
let mostRecent = channel.allPrivateMessages.last(where: { $0.channel == channel.index })
|
||||
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
|
||||
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
|
||||
|
|
@ -101,51 +104,50 @@ struct ChannelList: View {
|
|||
VStack {
|
||||
// Display Contacts for the rest of the non admin channels
|
||||
if let node, let myInfo = node.myInfo, let channels = myInfo.channels?.array as? [ChannelEntity] {
|
||||
List(channels, id: \.self, selection: $channelSelection) { (channel: ChannelEntity) in
|
||||
if !restrictedChannels.contains(channel.name?.lowercased() ?? "") {
|
||||
makeNavigationLink(myInfo: myInfo, channel: channel)
|
||||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
if channel.allPrivateMessages.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteChannelMessagesConfirm = true
|
||||
channelSelection = channel
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
Button {
|
||||
channel.mute = !channel.mute
|
||||
|
||||
do {
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!)
|
||||
if adminMessageId > 0 {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
List(selection: $channelSelection) {
|
||||
ForEach(channels) { (channel: ChannelEntity) in
|
||||
if !restrictedChannels.contains(channel.name?.lowercased() ?? "") {
|
||||
makeChannelRow(myInfo: myInfo, channel: channel)
|
||||
.frame(height: 62)
|
||||
.contextMenu {
|
||||
if channel.allPrivateMessages.count > 0 {
|
||||
Button(role: .destructive) {
|
||||
isPresentingDeleteChannelMessagesConfirm = true
|
||||
channelSelection = channel
|
||||
} label: {
|
||||
Label("Delete Messages", systemImage: "trash")
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
} catch {
|
||||
context.rollback()
|
||||
Logger.data.error("💥 Save Channel Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
|
||||
Button {
|
||||
channel.mute = !channel.mute
|
||||
do {
|
||||
let adminMessageId = bleManager.saveChannel(channel: channel.protoBuf, fromUser: node.user!, toUser: node.user!)
|
||||
if adminMessageId > 0 {
|
||||
context.refresh(channel, mergeChanges: true)
|
||||
}
|
||||
try context.save()
|
||||
} catch {
|
||||
context.rollback()
|
||||
Logger.data.error("💥 Save Channel Mute Error")
|
||||
}
|
||||
} label: {
|
||||
Label(channel.mute ? "Show Alerts" : "Hide Alerts", systemImage: channel.mute ? "bell" : "bell.slash")
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteChannelMessagesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteChannelMessages(channel: channelSelection!, context: context)
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
channelSelection = nil
|
||||
} label: {
|
||||
Text("delete")
|
||||
.confirmationDialog(
|
||||
"This conversation will be deleted.",
|
||||
isPresented: $isPresentingDeleteChannelMessagesConfirm,
|
||||
titleVisibility: .visible
|
||||
) {
|
||||
Button(role: .destructive) {
|
||||
deleteChannelMessages(channel: channelSelection!, context: context)
|
||||
context.refresh(myInfo, mergeChanges: true)
|
||||
channelSelection = nil
|
||||
} label: {
|
||||
Text("delete")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding([.top, .bottom])
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ struct Messages: View {
|
|||
|
||||
@Binding
|
||||
var unreadDirectMessages: Int
|
||||
|
||||
|
||||
// Aliases the navigation state for the NavigationSplitView sidebar selection
|
||||
private var messagesSelection: Binding<MessagesNavigationState?> {
|
||||
Binding(
|
||||
|
|
@ -88,28 +88,50 @@ struct Messages: View {
|
|||
.navigationBarTitleDisplayMode(.large)
|
||||
.navigationBarItems(leading: MeshtasticLogo())
|
||||
.onAppear {
|
||||
let nodeId = Int64(UserDefaults.preferredPeripheralNum)
|
||||
if nodeId > 0 {
|
||||
node = getNodeInfo(id: nodeId, context: context)
|
||||
}
|
||||
setupNavigationState()
|
||||
}
|
||||
} content: {
|
||||
if case .messages(let state) = router.navigationState {
|
||||
switch state {
|
||||
case .channels:
|
||||
// TODO: support linking to the channel
|
||||
ChannelList(node: node)
|
||||
case .directMessages(userNum: let userNum, messageId: _):
|
||||
UserList(
|
||||
node: node,
|
||||
selectedUserNum: userNum
|
||||
)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
if case .messages(.channels) = router.navigationState {
|
||||
ChannelList(node: $node, channelSelection: $channelSelection)
|
||||
} else if case .messages(.directMessages) = router.navigationState {
|
||||
UserList(node: $node, userSelection: $userSelection)
|
||||
}
|
||||
} detail: {
|
||||
if let myInfo = node?.myInfo, let channelSelection {
|
||||
ChannelMessageList(myInfo: myInfo, channel: channelSelection)
|
||||
} else if let userSelection {
|
||||
UserMessageList(user: userSelection)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupNavigationState() {
|
||||
let nodeId = Int64(UserDefaults.preferredPeripheralNum)
|
||||
if nodeId > 0 {
|
||||
node = getNodeInfo(id: nodeId, context: context)
|
||||
}
|
||||
|
||||
guard case .messages(let state) = router.navigationState else {
|
||||
return
|
||||
}
|
||||
|
||||
if let state {
|
||||
switch state {
|
||||
case .channels(channelId: let channelId, messageId: _):
|
||||
if let channelId {
|
||||
channelSelection = node?.myInfo?.channels?.first(where: { channel in
|
||||
guard let channel = channel as? ChannelEntity else { return false }
|
||||
return channel.id == channelId
|
||||
}) as? ChannelEntity
|
||||
}
|
||||
case .directMessages(userNum: let userNum, messageId: _):
|
||||
if let userNum {
|
||||
userSelection = getUser(id: userNum, context: context)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channelSelection = nil
|
||||
userSelection = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,19 +33,20 @@ struct UserList: View {
|
|||
sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false),
|
||||
NSSortDescriptor(key: "userNode.favorite", ascending: false),
|
||||
NSSortDescriptor(key: "longName", ascending: true)],
|
||||
animation: .default)
|
||||
|
||||
animation: .default
|
||||
)
|
||||
private var users: FetchedResults<UserEntity>
|
||||
@State var node: NodeInfoEntity?
|
||||
@State var selectedUserNum: Int64?
|
||||
@State private var userSelection: UserEntity? // Nothing selected by default.
|
||||
|
||||
@Binding var node: NodeInfoEntity?
|
||||
@Binding var userSelection: UserEntity?
|
||||
|
||||
@State private var isPresentingDeleteUserMessagesConfirm: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current)
|
||||
let dateFormatString = (localeDateFormat ?? "MM/dd/YY")
|
||||
VStack {
|
||||
List {
|
||||
List(selection: $userSelection) {
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
TipView(ContactsTip(), arrowEdge: .bottom)
|
||||
}
|
||||
|
|
@ -54,8 +55,8 @@ struct UserList: View {
|
|||
let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 ))))
|
||||
let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0
|
||||
let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0
|
||||
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
NavigationLink(destination: UserMessageList(user: user)) {
|
||||
if user.num != bleManager.connectedPeripheral?.num ?? 0 {
|
||||
NavigationLink(value: user) {
|
||||
ZStack {
|
||||
Image(systemName: "circle.fill")
|
||||
.opacity(user.unreadMessages > 0 ? 1 : 0)
|
||||
|
|
@ -205,9 +206,6 @@ struct UserList: View {
|
|||
.onChange(of: distanceFilter) { _ in
|
||||
searchUserList()
|
||||
}
|
||||
.onChange(of: selectedUserNum) { newUserNum in
|
||||
userSelection = users.first(where: { $0.num == newUserNum })
|
||||
}
|
||||
.onAppear {
|
||||
searchUserList()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue