Meshtastic-Apple/Meshtastic/Views/Settings/ShareChannels.swift
Garth Vander Houwen 51929b3a35 Swiftlint fixes
2024-06-02 18:32:14 -07:00

287 lines
11 KiB
Swift

//
// ShareChannel.swift
// MeshtasticApple
//
// Copyright(c) Garth Vander Houwen 4/8/22.
//
import SwiftUI
import CoreData
import CoreImage.CIFilterBuiltins
#if canImport(TipKit)
import TipKit
#endif
struct QrCodeImage {
let context = CIContext()
func generateQRCode(from text: String) -> UIImage {
var qrImage = UIImage(systemName: "xmark.circle") ?? UIImage()
let data = Data(text.utf8)
let filter = CIFilter.qrCodeGenerator()
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 20, y: 20)
if let outputImage = filter.outputImage?.transformed(by: transform) {
if let image = context.createCGImage(
outputImage,
from: outputImage.extent) {
qrImage = UIImage(cgImage: image)
}
}
return qrImage
}
}
struct ShareChannels: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@Environment(\.dismiss) private var dismiss
@State var channelSet: ChannelSet = ChannelSet()
@State var includeChannel0 = true
@State var includeChannel1 = true
@State var includeChannel2 = true
@State var includeChannel3 = true
@State var includeChannel4 = true
@State var includeChannel5 = true
@State var includeChannel6 = true
@State var includeChannel7 = true
@State var replaceChannels = true
var node: NodeInfoEntity?
@State private var channelsUrl = "https://www.meshtastic.org/e/#"
var qrCodeImage = QrCodeImage()
var body: some View {
if #available(iOS 17.0, macOS 14.0, *) {
VStack {
TipView(ShareChannelsTip(), arrowEdge: .bottom)
}
}
GeometryReader { bounds in
let smallest = min(bounds.size.width, bounds.size.height)
ScrollView {
if node != nil && node?.myInfo != nil {
Grid {
GridRow {
Spacer()
Text("include")
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
Text("channel")
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
Text("encrypted")
.font(.caption)
.fontWeight(.bold)
}
ForEach(node?.myInfo?.channels?.array as? [ChannelEntity] ?? [], id: \.self) { (channel: ChannelEntity) in
GridRow {
Spacer()
if channel.index == 0 {
Toggle("Channel 0 Included", isOn: $includeChannel0)
.toggleStyle(.switch)
.labelsHidden()
Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords())
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 1 && channel.role > 0 {
Toggle("Channel 1 Included", isOn: $includeChannel1)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 2 && channel.role > 0 {
Toggle("Channel 2 Included", isOn: $includeChannel2)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 3 && channel.role > 0 {
Toggle("Channel 3 Included", isOn: $includeChannel3)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 4 && channel.role > 0 {
Toggle("Channel 4 Included", isOn: $includeChannel4)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 5 && channel.role > 0 {
Toggle("Channel 5 Included", isOn: $includeChannel5)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 6 && channel.role > 0 {
Toggle("Channel 6 Included", isOn: $includeChannel6)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
} else if channel.index == 7 && channel.role > 0 {
Toggle("Channel 7 Included", isOn: $includeChannel7)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 1)
Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize()
if channel.psk?.hexDescription.count ?? 0 < 3 {
Image(systemName: "lock.slash")
.foregroundColor(.red)
} else {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
}
Spacer()
}
}
}
let qrImage = qrCodeImage.generateQRCode(from: channelsUrl)
VStack {
if node != nil {
Toggle(isOn: $replaceChannels) {
Label(replaceChannels ? "Replace Channels" : "Add Channels", systemImage: replaceChannels ? "arrow.triangle.2.circlepath.circle" : "plus.app")
}
.tint(.accentColor)
.toggleStyle(.button)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding(.top)
.padding(.bottom)
ShareLink("Share QR Code & Link",
item: Image(uiImage: qrImage),
subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"),
message: Text(channelsUrl),
preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you",
image: Image(uiImage: qrImage))
)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
.padding(.bottom)
Image(uiImage: qrImage)
.resizable()
.scaledToFit()
.frame(
minWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
maxWidth: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
minHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
maxHeight: smallest * (UIDevice.current.userInterfaceIdiom == .phone ? 0.8 : 0.6),
alignment: .top
)
}
}
}
}
.navigationTitle("generate.qr.code")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
})
.onAppear {
bleManager.context = context
generateChannelSet()
}
.onChange(of: includeChannel0) { _ in generateChannelSet() }
.onChange(of: includeChannel1) { _ in generateChannelSet() }
.onChange(of: includeChannel2) { _ in generateChannelSet() }
.onChange(of: includeChannel3) { _ in generateChannelSet() }
.onChange(of: includeChannel4) { _ in generateChannelSet() }
.onChange(of: includeChannel5) { _ in generateChannelSet() }
.onChange(of: includeChannel6) { _ in generateChannelSet() }
.onChange(of: includeChannel7) { _ in generateChannelSet() }
.onChange(of: replaceChannels) { _ in generateChannelSet() }
}
}
func generateChannelSet() {
channelSet = ChannelSet()
var loRaConfig = Config.LoRaConfig()
loRaConfig.region = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0))!.protoEnumValue()
loRaConfig.modemPreset = ModemPresets(rawValue: Int(node?.loRaConfig?.modemPreset ?? 0))!.protoEnumValue()
loRaConfig.bandwidth = UInt32(node?.loRaConfig?.bandwidth ?? 0)
loRaConfig.spreadFactor = UInt32(node?.loRaConfig?.spreadFactor ?? 0)
loRaConfig.codingRate = UInt32(node?.loRaConfig?.codingRate ?? 0)
loRaConfig.frequencyOffset = node?.loRaConfig?.frequencyOffset ?? 0
loRaConfig.hopLimit = UInt32(node?.loRaConfig?.hopLimit ?? 3)
loRaConfig.txEnabled = node?.loRaConfig?.txEnabled ?? false
loRaConfig.txPower = node?.loRaConfig?.txPower ?? 0
loRaConfig.usePreset = node?.loRaConfig?.usePreset ?? true
loRaConfig.channelNum = UInt32(node?.loRaConfig?.channelNum ?? 0)
loRaConfig.sx126XRxBoostedGain = node?.loRaConfig?.sx126xRxBoostedGain ?? false
loRaConfig.ignoreMqtt = node?.loRaConfig?.ignoreMqtt ?? false
channelSet.loraConfig = loRaConfig
if node?.myInfo?.channels != nil && node?.myInfo?.channels?.count ?? 0 > 0 {
for ch in node?.myInfo?.channels?.array as? [ChannelEntity] ?? [] {
if ch.role > 0, ch.index == 0 && includeChannel0 || ch.index == 1 && includeChannel1 || ch.index == 2 && includeChannel2 || ch.index == 3 && includeChannel3 ||
ch.index == 4 && includeChannel4 || ch.index == 5 && includeChannel5 || ch.index == 6 && includeChannel6 || ch.index == 7 && includeChannel7 {
var channelSettings = ChannelSettings()
channelSettings.name = ch.name!
channelSettings.psk = ch.psk!
channelSettings.id = UInt32(ch.id)
channelSet.settings.append(channelSettings)
}
}
guard let settingsString = try? channelSet.serializedData().base64EncodedString() else {
return
}
channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url() + (replaceChannels ? "" : "?add=true"))
}
}
}