Skip to content

Commit 32ad736

Browse files
committed
Add support for transliteration
1 parent 6de1ef8 commit 32ad736

File tree

4 files changed

+66
-18
lines changed

4 files changed

+66
-18
lines changed

InfiniLink/BLE/BLEWriteManager.swift

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//
2-
// Notifications.swift
2+
// BLEWriteManager.swift
33
// InfiniLink
44
//
5-
// Created by Alex Emry on 8/8/21.
5+
// Created by Liam Willey on 5/27/25.
66
//
77

88
import Foundation
@@ -13,6 +13,7 @@ struct BLEWriteManager {
1313
let bleManager = BLEManager.shared
1414

1515
@AppStorage("watchNotifications") var watchNotifications = true
16+
@AppStorage("transliterationEnabled") var transliterationEnabled = true
1617

1718
func writeToMusicApp(message: String, characteristic: CBCharacteristic) -> Void {
1819
guard bleManager.infiniTime != nil else { return }
@@ -47,24 +48,41 @@ struct BLEWriteManager {
4748

4849
func sendNotification(_ notif: AppNotification) {
4950
guard bleManager.infiniTime != nil else { return }
50-
guard let titleData = (" " + notif.title + "\0").data(using: .ascii) else { return }
51-
guard let bodyData = (notif.subtitle + "\0").data(using: .ascii) else { return }
5251

53-
var notification = titleData
52+
let title = transliterationEnabled ? notif.title.asciiSafe : notif.title
53+
let body = transliterationEnabled ? notif.subtitle.asciiSafe : notif.subtitle
5454

55-
notification.append(bodyData)
55+
// Convert strings to ASCII
56+
let titleData = (title + "\0").data(using: .ascii)
57+
let bodyData = (body + "\0").data(using: .ascii)
58+
59+
// Log if there was a failure when converting
60+
if titleData == nil {
61+
log("Failed to convert \(notif.title) to ASCII data", caller: "BLEWriteManager", target: .ble)
62+
}
63+
if bodyData == nil {
64+
log("Failed to convert \(notif.subtitle) to ASCII data", caller: "BLEWriteManager", target: .ble)
65+
}
66+
67+
// If either the strings couldn't be converted, don't send the notification
68+
if titleData == nil || bodyData == nil {
69+
return
70+
}
71+
72+
var notification = titleData!
73+
notification.append(bodyData!)
5674

5775
if !notification.isEmpty && watchNotifications {
5876
bleManager.infiniTime.writeValue(notification, for: bleManager.notifyCharacteristic, type: .withResponse)
59-
log("Notification sent with title: \(notif.title)", caller: "BLEWriteManager", target: .ble)
77+
log("Notification sent with title: \(title)", caller: "BLEWriteManager", target: .ble)
6078
}
6179
}
6280

6381
func sendLostNotification() {
6482
guard bleManager.infiniTime != nil else { return }
6583

6684
let hexPrefix = Data([0x03, 0x01, 0x00]) // Hexadecimal representation of "\x03\x01\x00"
67-
let nameData = "InfiniLink".data(using: .ascii) ?? Data()
85+
let nameData = "InfiniLink".data(using: .ascii)!
6886

6987
let notification = hexPrefix + nameData
7088

@@ -86,7 +104,10 @@ struct BLEWriteManager {
86104
guard var locationData = location.data(using: .ascii) else {
87105
log("Error encoding location string", caller: "BLEWriteManager")
88106

89-
for _ in 1...32 {bytes.append(0)}
107+
for _ in 1...32 {
108+
bytes.append(0)
109+
}
110+
90111
bytes.append(icon)
91112

92113
let writeData = Data(bytes: bytes as [UInt8], count: 49)
@@ -98,9 +119,13 @@ struct BLEWriteManager {
98119

99120
if locationData.count > 32 {
100121
log("Weather location string is too big to send", caller: "BLEWriteManager", target: .ble)
101-
for _ in 1...32 {bytes.append(0)}
122+
for _ in 1...32 {
123+
bytes.append(0)
124+
}
102125
} else {
103-
for _ in 1...32-locationData.count {locationData.append(0)}
126+
for _ in (1...32 - locationData.count) {
127+
locationData.append(0)
128+
}
104129
bytes.append(contentsOf: locationData)
105130
}
106131
bytes.append(icon)
@@ -124,14 +149,14 @@ struct BLEWriteManager {
124149
bytes.append(contentsOf: timeSince1970())
125150
bytes.append(UInt8(minimumTemperature.count))
126151

127-
for idx in 0...minimumTemperature.count-1 {
152+
for idx in (0...minimumTemperature.count - 1) {
128153
bytes.append(contentsOf: convertTemperature(value: Int(round(minimumTemperature[idx])))) // Minimum temperature
129154
bytes.append(contentsOf: convertTemperature(value: Int(round(maximumTemperature[idx])))) // Maximum temperature
130155
bytes.append(icon[idx])
131156
}
132157

133158
if minimumTemperature.count < 5 {
134-
for _ in 0...4-minimumTemperature.count {
159+
for _ in (0...4 - minimumTemperature.count) {
135160
bytes.append(contentsOf: [0, 0, 0, 0, 0])
136161
}
137162
}
@@ -149,7 +174,8 @@ struct BLEWriteManager {
149174
guard bleManager.navigationFlagsCharacteristic != nil && bleManager.navigationNarrativeCharacteristic != nil && bleManager.navigationDistanceCharacteristic != nil && bleManager.navigationProgressCharacteristic != nil && bleManager.infiniTime != nil else { return }
150175

151176
guard let icon = icon.data(using: .ascii) else { return }
152-
guard let narrative = instructions.data(using: .ascii) else { return }
177+
// The narrative may contain non-InfiniTime-readable characters, so transliterate if enabled
178+
guard let narrative = (transliterationEnabled ? instructions.asciiSafe : instructions).data(using: .ascii) else { return }
153179
guard let distance = distance.data(using: .ascii) else { return }
154180

155181
var progress = Data()
@@ -166,7 +192,7 @@ struct BLEWriteManager {
166192

167193
extension BLEWriteManager {
168194
func timeSince1970() -> [UInt8] {
169-
let timeInterval : UInt64 = UInt64(Date().timeIntervalSince1970)
195+
let timeInterval: UInt64 = UInt64(Date().timeIntervalSince1970)
170196

171197
let byte1 = UInt8(timeInterval & 0x00000000000000FF)
172198
let byte2 = UInt8((timeInterval & 0x000000000000FF00) >> 8)

InfiniLink/Core/Settings/Notifications/NotificationsSettingsView.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct NotificationsSettingsView: View {
2121
@AppStorage("watchNotifications") var watchNotifications = true
2222
@AppStorage("enableReminders") var enableReminders = true
2323
@AppStorage("enableCalendarNotifications") var enableCalendarNotifications = true
24-
24+
@AppStorage("transliterationEnabled") var transliterationEnabled = true
2525
@AppStorage("remindOnStepGoalCompletion") var remindOnStepGoalCompletion = true
2626

2727
@State private var reminderAuthStatus = EKEventStore.authorizationStatus(for: .reminder)
@@ -91,6 +91,11 @@ struct NotificationsSettingsView: View {
9191
Toggle("Reminder Notifications", isOn: $enableReminders)
9292
Toggle("Calendar Notifications", isOn: $enableCalendarNotifications)
9393
}
94+
Section {
95+
Toggle("Transliterate to ASCII", isOn: $transliterationEnabled)
96+
} footer: {
97+
Text("Convert accented characters to plain text so notifications display correctly.")
98+
}
9499
if authDenied(reminderAuthStatus) || authDenied(eventAuthStatus) {
95100
Section(footer: Text("To receive reminder notifications, you'll need to give InfiniLink read access to reminders and events.")) {
96101
Button("Allow Event Access") {

InfiniLink/Localizable.xcstrings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@
456456
},
457457
"Continue to Update" : {
458458

459+
},
460+
"Convert accented characters to plain text so notifications display correctly." : {
461+
459462
},
460463
"Converting..." : {
461464

@@ -1337,6 +1340,9 @@
13371340
},
13381341
"Track Resets" : {
13391342

1343+
},
1344+
"Transliterate to ASCII" : {
1345+
13401346
},
13411347
"Units" : {
13421348

InfiniLink/String+Extension.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,27 @@ extension String {
2323
/// Convert Hexadecimal String to Array<UInt>
2424
/// "0123".hex // [1, 35]
2525
/// "aabbccdd 00112233".hex // 170, 187, 204, 221, 0, 17, 34, 51]
26-
var hex : [UInt8] {
26+
var hex: [UInt8] {
2727
return convertHex(self.unicodeScalars, i: self.unicodeScalars.startIndex, appendTo: [])
2828
}
2929

3030
/// Convert Hexadecimal String to Data
3131
/// "0123".hexData /// 0123
3232
/// "aa bb cc dd 00 11 22 33".hexData /// aabbccdd 00112233
33-
var hexData : Data {
33+
var hexData: Data {
3434
return Data(convertHex(self.unicodeScalars, i: self.unicodeScalars.startIndex, appendTo: []))
3535
}
36+
37+
var asciiSafe: String {
38+
// Remove all non-InfiniTime-readable characters from the string
39+
// including emojis, symbols, accents
40+
let latinized = self
41+
.applyingTransform(.toLatin, reverse: false)?
42+
.applyingTransform(.stripDiacritics, reverse: false) ?? self
43+
44+
let filteredScalars = latinized.unicodeScalars.filter { $0.isASCII }
45+
return String(String.UnicodeScalarView(filteredScalars))
46+
}
3647
}
3748

3849
extension String: @retroactive Identifiable {

0 commit comments

Comments
 (0)