Merge pull request #1137 from meshtastic/emoji-improvements

Improvements to emoji handling for node names
This commit is contained in:
Garth Vander Houwen 2025-03-19 08:27:00 -07:00 committed by GitHub
commit 2fdaed2650
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 60 additions and 19 deletions

View file

@ -93,12 +93,51 @@ extension String {
// Filter out variation selectors from the string
var withoutVariationSelectors: String {
return self.unicodeScalars
.filter { scalar in
return !scalar.properties.isVariationSelector
var scalars: [UnicodeScalar] = []
var previousWasASCII = false
for scalar in self.unicodeScalars {
if scalar.properties.isVariationSelector {
// Only keep variation selector if the previous character was ASCII
if previousWasASCII {
scalars.append(scalar)
}
// No need to update previousWasASCII since variation selectors aren't characters
// Shouldn't have 2 in a row
} else {
scalars.append(scalar)
previousWasASCII = scalar.isASCII
}
.compactMap { UnicodeScalar($0) }
}
return scalars.compactMap { UnicodeScalar($0) }
.map { String($0) }
.joined()
}
// Adds variation selectors to prefer the graphical form of emoji.
// Looks ahead to make sure that the variation selector is not already applied.
var addingVariationSelectors: String {
var result = ""
let scalars = self.unicodeScalars
var index = scalars.startIndex
while index < scalars.endIndex {
let currentScalar = scalars[index]
result += String(currentScalar)
if currentScalar.properties.isEmoji && !currentScalar.properties.isEmojiPresentation && !currentScalar.isASCII {
// Check if the next scalar is U+FE0F
let nextIndex = scalars.index(after: index)
if nextIndex < scalars.endIndex && scalars[nextIndex].value == 0xFE0F {
// Already has variation selector; skip the next scalar
index = nextIndex
} else {
// Append variation selector
result += String(UnicodeScalar(0xFE0F)!)
}
}
// Move to the next scalar
index = scalars.index(after: index)
}
return result
}
}

View file

@ -61,9 +61,9 @@ struct Connect: View {
.padding(.trailing)
VStack(alignment: .leading) {
if node != nil {
Text(connectedPeripheral.longName).font(.title2)
Text(connectedPeripheral.longName.addingVariationSelectors).font(.title2)
}
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)")
Text("BLE Name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name?.addingVariationSelectors ?? "unknown".localized)")
.font(.callout).foregroundColor(Color.gray)
if node != nil {
Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)")
@ -120,7 +120,7 @@ struct Connect: View {
#endif
Text("Num: \(String(node!.num))")
Text("Short Name: \(node?.user?.shortName ?? "?")")
Text("Long Name: \(node?.user?.longName ?? "unknown".localized)")
Text("Long Name: \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
Text("BLE RSSI: \(connectedPeripheral.rssi)")
Button {
@ -333,7 +333,7 @@ struct Connect: View {
let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 4"))
let mostRecent = localStats?.lastObject as? TelemetryEntity
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown")
let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName?.addingVariationSelectors ?? "unknown")
let future = Date(timeIntervalSinceNow: Double(timerSeconds))
let initialContentState = MeshActivityAttributes.ContentState(uptimeSeconds: UInt32(mostRecent?.uptimeSeconds ?? 0),

View file

@ -16,7 +16,7 @@ struct CircleText: View {
Circle()
.fill(color)
.frame(width: circleSize, height: circleSize)
Text(text)
Text(text.addingVariationSelectors)
.frame(width: circleSize * 0.9, height: circleSize * 0.9, alignment: .center)
.foregroundColor(color.isLight() ? .black : .white)
.minimumScaleFactor(0.001)

View file

@ -28,7 +28,7 @@ struct ConnectedDevice: View {
.imageScale(.large)
.foregroundColor(.green)
.symbolRenderingMode(.hierarchical)
Text(name).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
Text(name.addingVariationSelectors).font(name.isEmoji() ? .title : .callout).foregroundColor(.gray)
} else {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.imageScale(.medium)

View file

@ -64,7 +64,7 @@ struct NodeListItem: View {
let (image, color) = userKeyStatus
IconAndText(systemName: image,
imageColor: color,
text: node.user?.longName ?? "unknown".localized,
text: node.user?.longName?.addingVariationSelectors ?? "unknown".localized,
textColor: .primary)
if node.favorite {
Spacer()

View file

@ -264,7 +264,7 @@ struct NodeList: View {
columnVisibility: columnVisibility
)
.edgesIgnoringSafeArea([.leading, .trailing])
.navigationBarTitle(String(node.user?.longName ?? "unknown".localized), displayMode: .inline)
.navigationBarTitle(String(node.user?.longName?.addingVariationSelectors ?? "unknown".localized), displayMode: .inline)
.navigationBarItems(
trailing: ZStack {
if UIDevice.current.userInterfaceIdiom != .phone {

View file

@ -341,7 +341,7 @@ struct Settings: View {
/// Connected Node
if node.num == bleManager.connectedPeripheral?.num ?? 0 {
Label {
Text("BLE: \(node.user?.longName ?? "unknown".localized)")
Text("BLE: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
} icon: {
Image(systemName: "antenna.radiowaves.left.and.right")
}
@ -363,14 +363,14 @@ struct Settings: View {
.tag(Int(node.num))
} else if UserDefaults.enableAdministration && node.user?.pkiEncrypted ?? false {
Label {
Text("Request PKI Admin: \(node.user?.longName ?? "unknown".localized)")
Text("Request PKI Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
} icon: {
Image(systemName: "rectangle.and.hand.point.up.left")
}
.tag(Int(node.num))
} else if !UserDefaults.enableAdministration {
Label {
Text("Request Legacy Admin: \(node.user?.longName ?? "unknown".localized)")
Text("Request Legacy Admin: \(node.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
} icon: {
Image(systemName: "rectangle.and.hand.point.up.left")
}
@ -395,7 +395,7 @@ struct Settings: View {
TipView(AdminChannelTip(), arrowEdge: .top)
} else {
if bleManager.connectedPeripheral != nil {
Text("Connected Node \(node?.user?.longName ?? "unknown".localized)")
Text("Connected Node \(node?.user?.longName?.addingVariationSelectors ?? "unknown".localized)")
}
}
}

View file

@ -50,12 +50,14 @@ struct UserConfig: View {
TextField("Long Name", text: $longName)
.onChange(of: longName) {
var totalBytes = longName.utf8.count
var newValue = longName.withoutVariationSelectors
var totalBytes = newValue.utf8.count
// Only mess with the value if it is too big
while totalBytes > (isLicensed ? 6 : 36) {
longName = String(longName.dropLast())
totalBytes = longName.utf8.count
newValue = String(newValue.dropLast())
totalBytes = newValue.utf8.count
}
longName = newValue
}
}
.keyboardType(.default)