Add exchange position node list menu item, add improved search to the user list.

This commit is contained in:
Garth Vander Houwen 2024-03-02 10:18:57 -08:00
parent 78b902dae0
commit 1db42dbbcd
3 changed files with 115 additions and 88 deletions

View file

@ -1039,9 +1039,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
} catch {
return false
}
return false
var meshPacket = MeshPacket()
meshPacket.to = UInt32(destNum)

View file

@ -17,12 +17,19 @@ struct UserList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@State private var searchText = ""
var usersQuery: Binding<String> {
Binding {
searchText
} set: { newValue in
searchText = newValue
users.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "longName CONTAINS[c] %@ OR shortName CONTAINS[c] %@", newValue, newValue)
/// Case Insensitive Search Text Predicates
let searchPredicates = ["userId", "hwModel", "longName", "shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText)
}
/// Create a compound predicate using each text search predicate as an OR
let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates)
users.nsPredicate = newValue.isEmpty ? nil : textSearchPredicate
}
}
@FetchRequest(
@ -48,94 +55,94 @@ struct UserList: View {
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)) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
VStack(alignment: .leading){
HStack{
Text(user.longName ?? "unknown".localized)
.font(.headline)
Spacer()
if user.vip {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
NavigationLink(destination: UserMessageList(user: user)) {
ZStack {
Image(systemName: "circle.fill")
.opacity(user.unreadMessages > 0 ? 1 : 0)
.font(.system(size: 10))
.foregroundColor(.accentColor)
.brightness(0.2)
}
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
VStack(alignment: .leading){
HStack{
Text(user.longName ?? "unknown".localized)
.font(.headline)
Spacer()
if user.vip {
Image(systemName: "star.fill")
.foregroundColor(.yellow)
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
if user.messageList.count > 0 {
if lastMessageDay == currentDay {
Text(lastMessageTime, style: .time )
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay == (currentDay - 1) {
Text("Yesterday")
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
.font(.footnote)
.foregroundColor(.secondary)
} else if lastMessageDay < (currentDay - 1800) {
Text(lastMessageTime.formattedDate(format: dateFormatString))
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
.font(.footnote)
.foregroundColor(.secondary)
}
}
}
if user.messageList.count > 0 {
HStack(alignment: .top) {
Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")")
.font(.footnote)
.foregroundColor(.secondary)
}
.frame(height: 62)
.contextMenu {
Button {
user.vip = !user.vip
do {
try context.save()
} catch {
context.rollback()
print("💥 Save User VIP Error")
}
} label: {
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
print("💥 Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
}
}
}
}
.frame(height: 62)
.contextMenu {
Button {
user.vip = !user.vip
do {
try context.save()
} catch {
context.rollback()
print("💥 Save User VIP Error")
}
} label: {
Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill")
}
Button {
user.mute = !user.mute
do {
try context.save()
} catch {
context.rollback()
print("💥 Save User Mute Error")
}
} label: {
Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash")
}
if user.messageList.count > 0 {
Button(role: .destructive) {
isPresentingDeleteUserMessagesConfirm = true
userSelection = user
} label: {
Label("Delete Messages", systemImage: "trash")
}
}
}
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
.confirmationDialog(
"This conversation will be deleted.",
isPresented: $isPresentingDeleteUserMessagesConfirm,
titleVisibility: .visible
) {
Button(role: .destructive) {
deleteUserMessages(user: userSelection!, context: context)
context.refresh(node!.user!, mergeChanges: true)
@ -149,7 +156,7 @@ struct UserList: View {
}
.listStyle(.plain)
.navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1)))
.searchable(text: usersQuery, prompt: "Find a contact")
.searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact")
}
}
}

View file

@ -36,6 +36,7 @@ struct NodeList: View {
@State private var isPresentingTraceRouteSentAlert = false
@State private var isPresentingClientHistorySentAlert = false
@State private var isPresentingDeleteNodeAlert = false
@State private var isPresentingPositionSentAlert = false
@State private var deleteNodeId: Int64 = 0
@State private var searchState = NodeSearchState()
@ -89,7 +90,21 @@ struct NodeList: View {
} label: {
Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash")
}
if connectedNodeNum != node.num {
if bleManager.connectedPeripheral != nil {
Button {
let positionSent = bleManager.sendPosition(
channel: node.channel,
destNum: node.num,
wantResponse: true
)
if positionSent {
isPresentingPositionSentAlert = true
}
} label: {
Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath")
}
}
if bleManager.connectedPeripheral != nil && connectedNodeNum != node.num {
Button {
let success = bleManager.sendTraceRouteRequest(destNum: node.user?.num ?? 0, wantResponse: true)
if success {
@ -120,6 +135,14 @@ struct NodeList: View {
}
}
}
.alert(
"Position Sent",
isPresented: $isPresentingPositionSentAlert
) {
Button("OK", role: .cancel) { }
} message: {
Text("Your position has been sent with a request for a response with their position.")
}
.alert(
"Trace Route Sent",
isPresented: $isPresentingTraceRouteSentAlert
@ -214,10 +237,10 @@ struct NodeList: View {
}
.navigationSplitViewStyle(.balanced)
.onChange(of: searchState.searchText) { _ in
runSearch()
searchNodeList()
}
.onChange(of: searchState.searchScope) { _ in
runSearch()
searchNodeList()
}
.onAppear {
if self.bleManager.context == nil {
@ -226,9 +249,9 @@ struct NodeList: View {
}
}
private func runSearch() {
private func searchNodeList() {
/// Case Insensitive Search Text Predicates
var searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in
return NSPredicate(format: "%K CONTAINS[c] %@", property, searchState.searchText)
}
/// Create a compound predicate using each text search preicate as an OR