mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
feat: local display names for nodes
- Add NodeDisplayNameStore (UserDefaults) keyed by node number - Add displayLongName/displayShortName on UserEntity - Show custom name in node list, detail, messages, relay text - Add EditNodeDisplayNameView sheet and 'Set display name' in list/detail - Notify UI on change via NodeDisplayNameStore.didChangeNotification Made-with: Cursor
This commit is contained in:
parent
d9e169142e
commit
59ff5ba96a
11 changed files with 221 additions and 44 deletions
|
|
@ -149,6 +149,8 @@
|
|||
DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0EA2C601795008C0C70 /* CLLocation.swift */; };
|
||||
DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */; };
|
||||
DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */; };
|
||||
DD0A10022E0292340090CE24 /* NodeDisplayNameStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0A10012E0292330090CE24 /* NodeDisplayNameStore.swift */; };
|
||||
DD0A10042E0292360090CE24 /* EditNodeDisplayNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0A10032E0292350090CE24 /* EditNodeDisplayNameView.swift */; };
|
||||
DD1BEF4A2E0292320090CE24 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BEF492E0292220090CE24 /* KeychainHelper.swift */; };
|
||||
DD1BEF4C2E030D310090CE24 /* KeyBackupStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BEF4B2E030D240090CE24 /* KeyBackupStatus.swift */; };
|
||||
DD1BEF4E2E03916A0090CE24 /* ChannelsHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BEF4D2E0391620090CE24 /* ChannelsHelp.swift */; };
|
||||
|
|
@ -487,6 +489,8 @@
|
|||
DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 42.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityConfig.swift; sourceTree = "<group>"; };
|
||||
DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 53.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD0A10012E0292330090CE24 /* NodeDisplayNameStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDisplayNameStore.swift; sourceTree = "<group>"; };
|
||||
DD0A10032E0292350090CE24 /* EditNodeDisplayNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditNodeDisplayNameView.swift; sourceTree = "<group>"; };
|
||||
DD1BEF492E0292220090CE24 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; };
|
||||
DD1BEF4B2E030D240090CE24 /* KeyBackupStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupStatus.swift; sourceTree = "<group>"; };
|
||||
DD1BEF4D2E0391620090CE24 /* ChannelsHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsHelp.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -1340,6 +1344,7 @@
|
|||
3D3417C72E29D38A006A988B /* GeoJSONOverlayConfig.swift */,
|
||||
BCD7448C2E0F2FA300F265A2 /* ContactURLHandler.swift */,
|
||||
DDD43FE12A78C86B0083A3E9 /* Mqtt */,
|
||||
DD0A10012E0292330090CE24 /* NodeDisplayNameStore.swift */,
|
||||
DD1BEF492E0292220090CE24 /* KeychainHelper.swift */,
|
||||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */,
|
||||
|
|
@ -1391,6 +1396,7 @@
|
|||
children = (
|
||||
DD4C11E02E8099C3003F2F2E /* PreferenceKeys */,
|
||||
108FFECA2DD3F43C00BFAA81 /* ShareContactQRDialog.swift */,
|
||||
DD0A10032E0292350090CE24 /* EditNodeDisplayNameView.swift */,
|
||||
231B3F232D087C020069A07D /* Metrics Columns */,
|
||||
DDAD49EB2AFAE82500B4425D /* Map */,
|
||||
DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */,
|
||||
|
|
@ -1791,6 +1797,8 @@
|
|||
BCB35B4F2E5FC42500B04F60 /* MessageNodeIntent.swift in Sources */,
|
||||
DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */,
|
||||
BCD7448D2E0F2FAA00F265A2 /* ContactURLHandler.swift in Sources */,
|
||||
DD0A10022E0292340090CE24 /* NodeDisplayNameStore.swift in Sources */,
|
||||
DD0A10042E0292360090CE24 /* EditNodeDisplayNameView.swift in Sources */,
|
||||
DD007BB02AA5981000F5FA12 /* NodeInfoEntityExtension.swift in Sources */,
|
||||
DDDB26422AABF655003AFCB7 /* NodeListItem.swift in Sources */,
|
||||
DDDB444629F8A96500EE2349 /* Character.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -59,9 +59,9 @@ extension MessageEntity {
|
|||
let users = try context.fetch(request)
|
||||
|
||||
// If exactly one match is found, return its name
|
||||
if users.count == 1, let name = users.first?.longName, !name.isEmpty
|
||||
{
|
||||
return "\(name)"
|
||||
if users.count == 1 {
|
||||
let name = users.first!.displayLongName
|
||||
if !name.isEmpty { return name }
|
||||
}
|
||||
|
||||
// If no exact match, find the node with the smallest hopsAway
|
||||
|
|
@ -72,8 +72,9 @@ extension MessageEntity {
|
|||
return false
|
||||
}
|
||||
return lhsHops < rhsHops
|
||||
}), let name = closestNode.longName, !name.isEmpty {
|
||||
return "\(name)"
|
||||
}) {
|
||||
let name = closestNode.displayLongName
|
||||
if !name.isEmpty { return name }
|
||||
}
|
||||
|
||||
// Fallback to hex node number if no matches
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
|
||||
extension NodeInfoEntity {
|
||||
extension NodeInfoEntity: Identifiable {
|
||||
public var id: NSManagedObjectID { objectID }
|
||||
|
||||
var latestPosition: PositionEntity? {
|
||||
return self.positions?.lastObject as? PositionEntity
|
||||
|
|
|
|||
|
|
@ -65,6 +65,24 @@ extension UserEntity {
|
|||
// Backwards-compatible property (uses viewContext)
|
||||
var unreadMessages: Int { unreadMessages(context: PersistenceController.shared.container.viewContext) }
|
||||
|
||||
/// Local display name for this node (if set), otherwise the device longName.
|
||||
var displayLongName: String {
|
||||
if let custom = NodeDisplayNameStore.displayName(for: num) {
|
||||
return custom
|
||||
}
|
||||
return longName ?? "Unknown".localized
|
||||
}
|
||||
|
||||
/// Short label for this node: first 4 characters of display name if set, otherwise device shortName.
|
||||
var displayShortName: String {
|
||||
if let custom = NodeDisplayNameStore.displayName(for: num) {
|
||||
let trimmed = custom.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmed.isEmpty { return shortName ?? "?" }
|
||||
return String(trimmed.prefix(4))
|
||||
}
|
||||
return shortName ?? "?"
|
||||
}
|
||||
|
||||
/// SVG Images for Vendors who are signed project backers
|
||||
var hardwareImage: String? {
|
||||
guard let hwModel else { return nil }
|
||||
|
|
|
|||
51
Meshtastic/Helpers/NodeDisplayNameStore.swift
Normal file
51
Meshtastic/Helpers/NodeDisplayNameStore.swift
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// NodeDisplayNameStore.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Local display names for nodes (keyed by node num). Used only for UI; device identity unchanged.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum NodeDisplayNameStore {
|
||||
private static let key = "nodeDisplayNames"
|
||||
|
||||
/// Posted when any display name is set or cleared so UI can refresh.
|
||||
static let didChangeNotification = Notification.Name("NodeDisplayNameStoreDidChange")
|
||||
|
||||
/// Returns the local display name for a node, or nil if none is set.
|
||||
static func displayName(for nodeNum: Int64) -> String? {
|
||||
let all = load()
|
||||
return all[storageKey(nodeNum)]
|
||||
}
|
||||
|
||||
/// Sets the local display name for a node. Pass nil to clear.
|
||||
static func setDisplayName(_ name: String?, for nodeNum: Int64) {
|
||||
var all = load()
|
||||
let key = storageKey(nodeNum)
|
||||
if let name = name?.trimmingCharacters(in: .whitespacesAndNewlines), !name.isEmpty {
|
||||
all[key] = name
|
||||
} else {
|
||||
all.removeValue(forKey: key)
|
||||
}
|
||||
save(all)
|
||||
NotificationCenter.default.post(name: didChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
private static func storageKey(_ nodeNum: Int64) -> String {
|
||||
String(nodeNum)
|
||||
}
|
||||
|
||||
private static func load() -> [String: String] {
|
||||
guard let data = UserDefaults.standard.data(forKey: key),
|
||||
let decoded = try? JSONDecoder().decode([String: String].self, from: data) else {
|
||||
return [:]
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
private static func save(_ dict: [String: String]) {
|
||||
guard let data = try? JSONEncoder().encode(dict) else { return }
|
||||
UserDefaults.standard.set(data, forKey: key)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +114,7 @@ fileprivate struct FilteredUserList: View {
|
|||
.brightness(0.2)
|
||||
}
|
||||
|
||||
CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num))))
|
||||
CircleText(text: user.displayShortName, color: Color(UIColor(hex: UInt32(user.num))))
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
|
|
@ -131,7 +131,7 @@ fileprivate struct FilteredUserList: View {
|
|||
Image(systemName: "lock.open.fill")
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
Text(user.longName ?? "Unknown".localized)
|
||||
Text(user.displayLongName)
|
||||
.font(.headline)
|
||||
.allowsTightening(true)
|
||||
Spacer()
|
||||
|
|
|
|||
60
Meshtastic/Views/Nodes/Helpers/EditNodeDisplayNameView.swift
Normal file
60
Meshtastic/Views/Nodes/Helpers/EditNodeDisplayNameView.swift
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// EditNodeDisplayNameView.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Sheet to set or clear a local display name for a node.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
struct EditNodeDisplayNameView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let node: NodeInfoEntity
|
||||
@State private var displayName: String = ""
|
||||
@State private var hasChanges: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
TextField("Display name", text: $displayName)
|
||||
.autocorrectionDisabled(true)
|
||||
.onChange(of: displayName) { _, _ in hasChanges = true }
|
||||
} footer: {
|
||||
Text("This name is only shown on this device. The node’s real name is unchanged for sharing and export.")
|
||||
}
|
||||
if NodeDisplayNameStore.displayName(for: node.num) != nil {
|
||||
Section {
|
||||
Button(role: .destructive) {
|
||||
displayName = ""
|
||||
hasChanges = true
|
||||
} label: {
|
||||
Label("Remove custom name", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Display name")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") {
|
||||
let trimmed = displayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
NodeDisplayNameStore.setDisplayName(trimmed.isEmpty ? nil : trimmed, for: node.num)
|
||||
dismiss()
|
||||
}
|
||||
.disabled(!hasChanges)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
displayName = NodeDisplayNameStore.displayName(for: node.num) ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,9 @@ struct NodeDetail: View {
|
|||
@ObservedObject var node: NodeInfoEntity
|
||||
@State private var environmentSectionHeight: CGFloat = 0
|
||||
@State var showingCompassSheet = false
|
||||
|
||||
@State private var showingDisplayNameSheet = false
|
||||
@State private var displayNameRefresh = 0
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollViewReader { scrollView in
|
||||
|
|
@ -49,11 +51,11 @@ struct NodeDetail: View {
|
|||
Section("Node") { // Node
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
CircleText(
|
||||
text: node.user?.shortName ?? "?",
|
||||
color: Color(UIColor(hex: UInt32(node.num))),
|
||||
circleSize: 75
|
||||
)
|
||||
CircleText(
|
||||
text: node.user?.displayShortName ?? "?",
|
||||
color: Color(UIColor(hex: UInt32(node.num))),
|
||||
circleSize: 75
|
||||
)
|
||||
if node.snr != 0 && !node.viaMqtt && node.hopsAway == 0 {
|
||||
Spacer()
|
||||
VStack {
|
||||
|
|
@ -120,6 +122,23 @@ struct NodeDetail: View {
|
|||
.textSelection(.enabled)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
Button {
|
||||
showingDisplayNameSheet = true
|
||||
} label: {
|
||||
HStack {
|
||||
Label {
|
||||
Text("Display name")
|
||||
} icon: {
|
||||
Image(systemName: "pencil.circle")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
Spacer()
|
||||
Text(node.user?.displayLongName ?? "—")
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
let connectedNode = getNodeInfo(id: accessoryManager.activeDeviceNum ?? 0, context: context)
|
||||
if let user = node.user, user.keyMatch {
|
||||
let publicKey = node.num == connectedNode?.num
|
||||
|
|
@ -575,14 +594,22 @@ struct NodeDetail: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingCompassSheet) {
|
||||
CompassView(waypointLocation: node.latestPosition?.nodeCoordinate ?? nil, waypointName: node.user?.longName ?? nil, color: Color(UIColor(hex: UInt32(node.num))))
|
||||
}
|
||||
.sheet(isPresented: $showingCompassSheet) {
|
||||
CompassView(waypointLocation: node.latestPosition?.nodeCoordinate ?? nil, waypointName: node.user?.displayLongName, color: Color(UIColor(hex: UInt32(node.num))))
|
||||
}
|
||||
.sheet(isPresented: $showingDisplayNameSheet) {
|
||||
EditNodeDisplayNameView(node: node)
|
||||
.onDisappear { displayNameRefresh += 1 }
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: NodeDisplayNameStore.didChangeNotification)) { _ in
|
||||
displayNameRefresh += 1
|
||||
}
|
||||
.onAppear {
|
||||
scrollView.scrollTo("topOfList", anchor: .top)
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle(String(node.user?.longName?.addingVariationSelectors ?? "Unknown".localized))
|
||||
.navigationTitle(String((node.user?.displayLongName ?? "Unknown".localized).addingVariationSelectors))
|
||||
.id(displayNameRefresh)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ struct NodeListItem: View {
|
|||
|
||||
private var accessibilityDescription: String {
|
||||
var desc = ""
|
||||
if let shortName = node.user?.shortName {
|
||||
desc = shortName.formatNodeNameForVoiceOver()
|
||||
} else if let longName = node.user?.longName {
|
||||
desc = longName
|
||||
} else {
|
||||
desc = "Unknown".localized + " " + "Node".localized
|
||||
}
|
||||
let shortName = node.user?.displayShortName ?? "?"
|
||||
let longName = node.user?.displayLongName ?? "Unknown".localized
|
||||
desc = shortName.formatNodeNameForVoiceOver()
|
||||
if desc.isEmpty { desc = longName }
|
||||
if desc.isEmpty { desc = "Unknown".localized + " " + "Node".localized }
|
||||
if isDirectlyConnected {
|
||||
desc += ", currently connected"
|
||||
}
|
||||
|
|
@ -128,7 +126,7 @@ struct NodeListItem: View {
|
|||
LazyVStack(alignment: .leading) {
|
||||
HStack {
|
||||
VStack(alignment: .center) {
|
||||
CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
CircleText(text: node.user?.displayShortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70)
|
||||
.padding(.trailing, 5)
|
||||
if node.latestDeviceMetrics != nil {
|
||||
BatteryCompact(batteryLevel: node.latestDeviceMetrics?.batteryLevel ?? 0, font: .caption, iconFont: .callout, color: .accentColor)
|
||||
|
|
@ -138,10 +136,10 @@ struct NodeListItem: View {
|
|||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
let (image, color) = userKeyStatus
|
||||
IconAndText(systemName: image,
|
||||
imageColor: color,
|
||||
text: node.user?.longName?.addingVariationSelectors ?? "Unknown".localized,
|
||||
textColor: .primary)
|
||||
IconAndText(systemName: image,
|
||||
imageColor: color,
|
||||
text: (node.user?.displayLongName ?? "Unknown".localized).addingVariationSelectors,
|
||||
textColor: .primary)
|
||||
if node.favorite {
|
||||
Spacer()
|
||||
Image(systemName: "star.fill")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ struct NodeList: View {
|
|||
@State private var isPresentingDeleteNodeAlert = false
|
||||
@State private var deleteNodeId: Int64 = 0
|
||||
@State private var shareContactNode: NodeInfoEntity?
|
||||
@State private var nodeForDisplayNameEdit: NodeInfoEntity?
|
||||
@StateObject var filters = NodeFilterParameters()
|
||||
@State var isEditingFilters = false
|
||||
@SceneStorage("selectedDetailView") var selectedDetailView: String?
|
||||
|
|
@ -40,7 +41,8 @@ struct NodeList: View {
|
|||
connectedNode: connectedNode,
|
||||
isPresentingDeleteNodeAlert: $isPresentingDeleteNodeAlert,
|
||||
deleteNodeId: $deleteNodeId,
|
||||
shareContactNode: $shareContactNode
|
||||
shareContactNode: $shareContactNode,
|
||||
nodeForDisplayNameEdit: $nodeForDisplayNameEdit
|
||||
)
|
||||
.sheet(isPresented: $isEditingFilters) {
|
||||
NodeListFilter(
|
||||
|
|
@ -93,16 +95,19 @@ struct NodeList: View {
|
|||
do {
|
||||
try await accessoryManager.removeNode(node: node, connectedNodeNum: Int64(accessoryManager.activeDeviceNum ?? -1))
|
||||
} catch {
|
||||
Logger.data.error("Failed to delete node \(node.user?.longName ?? "Unknown".localized, privacy: .public)")
|
||||
Logger.data.error("Failed to delete node \(node.user?.displayLongName ?? "Unknown".localized, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(item: $shareContactNode) { selectedNode in
|
||||
ShareContactQRDialog(node: selectedNode.toProto())
|
||||
}
|
||||
.sheet(item: $shareContactNode) { selectedNode in
|
||||
ShareContactQRDialog(node: selectedNode.toProto())
|
||||
}
|
||||
.sheet(item: $nodeForDisplayNameEdit) { node in
|
||||
EditNodeDisplayNameView(node: node)
|
||||
}
|
||||
.navigationSplitViewColumnWidth(min: 100, ideal: 300, max: .infinity)
|
||||
.navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack {
|
||||
ConnectedDevice(
|
||||
|
|
@ -160,6 +165,7 @@ fileprivate struct FilteredNodeList: View {
|
|||
@Binding var isPresentingDeleteNodeAlert: Bool
|
||||
@Binding var deleteNodeId: Int64
|
||||
@Binding var shareContactNode: NodeInfoEntity?
|
||||
@Binding var nodeForDisplayNameEdit: NodeInfoEntity?
|
||||
|
||||
// The initializer for the FetchRequest
|
||||
init(
|
||||
|
|
@ -168,7 +174,8 @@ fileprivate struct FilteredNodeList: View {
|
|||
connectedNode: NodeInfoEntity?,
|
||||
isPresentingDeleteNodeAlert: Binding<Bool>,
|
||||
deleteNodeId: Binding<Int64>,
|
||||
shareContactNode: Binding<NodeInfoEntity?>
|
||||
shareContactNode: Binding<NodeInfoEntity?>,
|
||||
nodeForDisplayNameEdit: Binding<NodeInfoEntity?>
|
||||
) {
|
||||
let request: NSFetchRequest<NodeInfoEntity> = NodeInfoEntity.fetchRequest()
|
||||
request.sortDescriptors = [
|
||||
|
|
@ -185,6 +192,7 @@ fileprivate struct FilteredNodeList: View {
|
|||
self._isPresentingDeleteNodeAlert = isPresentingDeleteNodeAlert
|
||||
self._deleteNodeId = deleteNodeId
|
||||
self._shareContactNode = shareContactNode
|
||||
self._nodeForDisplayNameEdit = nodeForDisplayNameEdit
|
||||
}
|
||||
|
||||
// The body of the view
|
||||
|
|
@ -213,6 +221,11 @@ fileprivate struct FilteredNodeList: View {
|
|||
node: NodeInfoEntity,
|
||||
connectedNode: NodeInfoEntity?
|
||||
) -> some View {
|
||||
Button {
|
||||
nodeForDisplayNameEdit = node
|
||||
} label: {
|
||||
Label("Set display name", systemImage: "pencil.circle")
|
||||
}
|
||||
if let user = node.user {
|
||||
NodeAlertsButton(context: context, node: node, user: user)
|
||||
if !user.unmessagable && user.num == UserDefaults.preferredPeripheralNum {
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ struct NodeRow: View {
|
|||
|
||||
HStack {
|
||||
|
||||
CircleText(text: node.user?.shortName ?? "???", color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
CircleText(text: node.user?.displayShortName ?? "???", color: Color.accentColor).offset(y: 1).padding(.trailing, 5)
|
||||
.offset(x: -15)
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
Text(node.user?.longName ?? "Unknown").font(.headline)
|
||||
.offset(x: -15)
|
||||
} else {
|
||||
Text(node.user?.longName ?? "Unknown").font(.title)
|
||||
.offset(x: -15)
|
||||
}
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
Text(node.user?.displayLongName ?? "Unknown").font(.headline)
|
||||
.offset(x: -15)
|
||||
} else {
|
||||
Text(node.user?.displayLongName ?? "Unknown").font(.title)
|
||||
.offset(x: -15)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue