Meshtastic-Apple/Meshtastic/Views/Settings/ShareChannels.swift
2022-10-12 20:13:03 -07:00

301 lines
10 KiB
Swift

//
// ShareChannel.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 4/8/22.
//
import SwiftUI
import CoreData
import CoreImage.CIFilterBuiltins
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
@EnvironmentObject var userSettings: UserSettings
@State var initialLoad: Bool = true
@State var channelSet: ChannelSet = ChannelSet()
@State var includeChannel0 = true
@State var includeChannel1 = false
@State var includeChannel2 = false
@State var includeChannel3 = false
@State var includeChannel4 = false
@State var includeChannel5 = false
@State var includeChannel6 = false
@State var includeChannel7 = false
@State var isPresentingHelp = false
var node: NodeInfoEntity?
@State private var channelsUrl = "https://www.meshtastic.org/e/#"
var qrCodeImage = QrCodeImage()
var body: some View {
VStack {
GeometryReader { bounds in
let smallest = min(bounds.size.width, bounds.size.height)
ScrollView {
VStack {
if node != nil {
Grid(alignment: .top, horizontalSpacing: 2) {
GridRow {
Spacer()
Text("Include")
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
Text("Channel")
.font(.caption)
.fontWeight(.bold)
.padding(.trailing)
Text("Encrypted")
.font(.caption)
.fontWeight(.bold)
Spacer()
}
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()
.disabled(channel.role == 1)
Text((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary")
} else if channel.index == 1 {
Toggle("Channel 1 Included", isOn: $includeChannel1)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 2 {
Toggle("Channel 2 Included", isOn: $includeChannel2)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 3 {
Toggle("Channel 3 Included", isOn: $includeChannel3)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 4 {
Toggle("Channel 4 Included", isOn: $includeChannel4)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 5 {
Toggle("Channel 5 Included", isOn: $includeChannel5)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 6 {
Toggle("Channel 6 Included", isOn: $includeChannel6)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
} else if channel.index == 7 {
Toggle("Channel 7 Included", isOn: $includeChannel7)
.toggleStyle(.switch)
.labelsHidden()
.disabled(channel.role == 0)
Text((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)")
}
if channel.role == 0 {
Image(systemName: "lock.slash")
.foregroundColor(.gray)
}
else if channel.role > 0 {
Image(systemName: "lock.fill")
.foregroundColor(.green)
}
Spacer()
}
}
}
}
}
let qrImage = qrCodeImage.generateQRCode(from: channelsUrl)
VStack {
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)
Image(uiImage: qrImage)
.resizable()
.scaledToFit()
.frame(
minWidth: smallest * 0.65,
maxWidth: smallest * 0.65,
minHeight: smallest * 0.65,
maxHeight: smallest * 0.65,
alignment: .top
)
Button {
isPresentingHelp = true
} label: {
Label("Help Me!", systemImage: "lifepreserver")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.small)
}
}
.sheet(isPresented: $isPresentingHelp) {
VStack {
Text("Meshtastic Channels").font(.title)
Text("A Meshtastic LoRa Mesh network can have up to 8 distinct channels.")
.font(.headline)
.padding(.bottom)
Text("Primary Channel").font(.title2)
Text("The first channel is the Primary channel and is where much of the mesh activity takes place. DM's are only available on the primary channel and it can not be disabled.")
.font(.callout)
.padding([.leading,.trailing,.bottom])
Text("Admin Channel").font(.title2)
Text("A channel with the name 'admin' is the Admin channel and can be used to remotely administer nodes on your mesh, text messages can not be sent over the admin channel.")
.font(.callout)
.padding([.leading,.trailing,.bottom])
Text("Private Channels").font(.title2)
Text("The other six channels can be used for private group converations. Each of these groups has its own encryption key.")
.font(.callout)
.padding([.leading,.trailing,.bottom])
Text("From this view your primary channel and mesh settings are always shared in the generated QR code and you can toggle to include your admin channel and any private groups you want the person you are sharing with to have access to.")
.font(.callout)
.padding([.leading,.trailing,.bottom])
Divider()
}
.padding()
.presentationDetents([.large])
.presentationDragIndicator(.automatic)
}
.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 {
if self.initialLoad{
self.bleManager.context = context
self.initialLoad = false
GenerateChannelSet()
}
}
.onChange(of: includeChannel1) { includeCh1 in
GenerateChannelSet()
}
.onChange(of: includeChannel2) { includeCh2 in
GenerateChannelSet()
}
.onChange(of: includeChannel3) { includeCh3 in
GenerateChannelSet()
}
.onChange(of: includeChannel4) { includeCh4 in
GenerateChannelSet()
}
.onChange(of: includeChannel5) { includeCh5 in
GenerateChannelSet()
}
.onChange(of: includeChannel6) { includeCh6 in
GenerateChannelSet()
}
.onChange(of: includeChannel7) { includeCh7 in
GenerateChannelSet()
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
func GenerateChannelSet() {
channelSet = ChannelSet()
var loRaConfig = Config.LoRaConfig()
loRaConfig.region = RegionCodes(rawValue: Int(node!.loRaConfig!.regionCode))!.protoEnumValue()
loRaConfig.modemPreset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset))!.protoEnumValue()
loRaConfig.bandwidth = UInt32(node!.loRaConfig!.bandwidth)
loRaConfig.spreadFactor = UInt32(node!.loRaConfig!.spreadFactor)
loRaConfig.codingRate = UInt32(node!.loRaConfig!.codingRate)
loRaConfig.frequencyOffset = node!.loRaConfig!.frequencyOffset
loRaConfig.hopLimit = UInt32(node!.loRaConfig!.hopLimit)
loRaConfig.txEnabled = node!.loRaConfig!.txEnabled
loRaConfig.txPower = node!.loRaConfig!.txPower
loRaConfig.channelNum = UInt32(node!.loRaConfig!.channelNum)
channelSet.loraConfig = loRaConfig
for ch in node!.myInfo!.channels!.array as! [ChannelEntity] {
if ch.role > 0 {
if 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)
channelSettings.uplinkEnabled = ch.uplinkEnabled
channelSettings.downlinkEnabled = ch.downlinkEnabled
channelSet.settings.append(channelSettings)
}
}
}
let settingsString = try! channelSet.serializedData().base64EncodedString()
channelsUrl = ("https://meshtastic.org/e/#" + settingsString.base64ToBase64url())
}
}