Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions LoopFollow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
654134182E1DC09700BDBE08 /* OverridePresetsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654134172E1DC09700BDBE08 /* OverridePresetsView.swift */; };
6541341A2E1DC27900BDBE08 /* OverridePresetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654134192E1DC27900BDBE08 /* OverridePresetData.swift */; };
6541341C2E1DC28000BDBE08 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6541341B2E1DC28000BDBE08 /* DateExtensions.swift */; };
656F8C102E49F36F0008DC1D /* QRCodeDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C0F2E49F36F0008DC1D /* QRCodeDisplayView.swift */; };
656F8C122E49F3780008DC1D /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */; };
656F8C142E49F3D20008DC1D /* RemoteCommandSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */; };
65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
6584B1012E4A263900135D4D /* TOTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6584B1002E4A263900135D4D /* TOTPService.swift */; };
DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
DD0247712DB4337700FCADF6 /* BuildExpireCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */; };
Expand Down Expand Up @@ -398,6 +402,10 @@
654134172E1DC09700BDBE08 /* OverridePresetsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePresetsView.swift; sourceTree = "<group>"; };
654134192E1DC27900BDBE08 /* OverridePresetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverridePresetData.swift; sourceTree = "<group>"; };
6541341B2E1DC28000BDBE08 /* DateExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensions.swift; sourceTree = "<group>"; };
656F8C0F2E49F36F0008DC1D /* QRCodeDisplayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeDisplayView.swift; sourceTree = "<group>"; };
656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeGenerator.swift; sourceTree = "<group>"; };
656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteCommandSettings.swift; sourceTree = "<group>"; };
65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
6584B1002E4A263900135D4D /* TOTPService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPService.swift; sourceTree = "<group>"; };
A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmCondition.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -914,6 +922,8 @@
DD4878062C7B2E9E0048F05C /* Settings */ = {
isa = PBXGroup;
children = (
65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */,
656F8C132E49F3D20008DC1D /* RemoteCommandSettings.swift */,
DD4878072C7B30BF0048F05C /* RemoteSettingsView.swift */,
DD4878092C7B30D40048F05C /* RemoteSettingsViewModel.swift */,
);
Expand Down Expand Up @@ -1218,6 +1228,7 @@
DDF6999C2C5AAA4C0058A8D9 /* Views */ = {
isa = PBXGroup;
children = (
656F8C0F2E49F36F0008DC1D /* QRCodeDisplayView.swift */,
DDE75D2C2DE71401007C1FC1 /* TogglableSecureInput.swift */,
DDE75D222DE5E505007C1FC1 /* Glyph.swift */,
DD8316492DE4C504004467AA /* SettingsStepperRow.swift */,
Expand Down Expand Up @@ -1486,6 +1497,7 @@
FCC688542489367300A0279D /* Helpers */ = {
isa = PBXGroup;
children = (
656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */,
DD4A407D2E6AFEE6007B318B /* AuthService.swift */,
DD1D52B82E1EB5DC00432050 /* TabPosition.swift */,
DD83164B2DE4DB3A004467AA /* BinaryFloatingPoint+localized.swift */,
Expand Down Expand Up @@ -1877,10 +1889,12 @@
DDF6999B2C5AA32E0058A8D9 /* TempTargetPreset.swift in Sources */,
DD7F4C0F2DD51EC200D449E9 /* TempTargetStartCondition.swift in Sources */,
DDBD19962DFB44B0005C2D69 /* Alarm+byPriorityThenSpec.swift in Sources */,
656F8C102E49F36F0008DC1D /* QRCodeDisplayView.swift in Sources */,
DDC6CA3D2DD7C6090060EE25 /* TemporaryCondition.swift in Sources */,
DD9ACA0E2D340BFF00415D8A /* AlarmTask.swift in Sources */,
DDCF9A822D85FD15004DF4DD /* AlarmType.swift in Sources */,
DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
656F8C122E49F3780008DC1D /* QRCodeGenerator.swift in Sources */,
6541341A2E1DC27900BDBE08 /* OverridePresetData.swift in Sources */,
FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
DD7F4C092DD504A700D449E9 /* OverrideStartCondition.swift in Sources */,
Expand All @@ -1897,6 +1911,7 @@
DD7F4C072DD5042F00D449E9 /* OverrideStartAlarmEditor.swift in Sources */,
DDCC3A4B2DDBB5E4006F1C10 /* BatteryCondition.swift in Sources */,
DDDF6F492D479AF000884336 /* NoRemoteView.swift in Sources */,
656F8C142E49F3D20008DC1D /* RemoteCommandSettings.swift in Sources */,
DD12D4872E1705E6004E0112 /* AlarmsContainerView.swift in Sources */,
DD83164A2DE4C504004467AA /* SettingsStepperRow.swift in Sources */,
DD0650ED2DCE9371004D3B41 /* HighBgAlarmEditor.swift in Sources */,
Expand Down Expand Up @@ -2049,6 +2064,7 @@
DD0650EF2DCE96FF004D3B41 /* HighBGCondition.swift in Sources */,
DDC6CA472DD8D9010060EE25 /* PumpChangeAlarmEditor.swift in Sources */,
DD4878132C7B750D0048F05C /* TempTargetView.swift in Sources */,
65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */,
DD0C0C682C48529400DBADDF /* Metric.swift in Sources */,
FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */,
DDC6CA432DD8CED20060EE25 /* SensorAgeCondition.swift in Sources */,
Expand Down
95 changes: 95 additions & 0 deletions LoopFollow/Helpers/QRCodeGenerator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// LoopFollow
// QRCodeGenerator.swift
// Created by codebymini.

import CoreImage
import UIKit

enum QRCodeGenerator {
/// Generates a QR code image from a string
/// - Parameters:
/// - string: The string to encode in the QR code
/// - size: The size of the generated image (default: 200x200)
/// - correctionLevel: The error correction level (default: .M)
/// - Returns: A UIImage containing the QR code, or nil if generation fails
static func generateQRCode(
from string: String,
size: CGSize = CGSize(width: 200, height: 200),
correctionLevel: String = "M"
) -> UIImage? {
// Create a CIFilter for QR code generation
guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
return nil
}

// Set the input data (the string to encode)
let data = string.data(using: .utf8)
filter.setValue(data, forKey: "inputMessage")

// Set the error correction level
filter.setValue(correctionLevel, forKey: "inputCorrectionLevel")

// Get the output image
guard let outputImage = filter.outputImage else {
return nil
}

// Scale the image to the desired size
let scaleX = size.width / outputImage.extent.size.width
let scaleY = size.height / outputImage.extent.size.height
let scaledImage = outputImage.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY))

// Convert CIImage to UIImage
let context = CIContext()
guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else {
return nil
}

return UIImage(cgImage: cgImage)
}

/// Generates a QR code image with custom colors
/// - Parameters:
/// - string: The string to encode in the QR code
/// - size: The size of the generated image (default: 200x200)
/// - foregroundColor: The color of the QR code (default: black)
/// - backgroundColor: The background color (default: white)
/// - correctionLevel: The error correction level (default: .M)
/// - Returns: A UIImage containing the QR code, or nil if generation fails
static func generateQRCode(
from string: String,
size: CGSize = CGSize(width: 200, height: 200),
foregroundColor: UIColor = .black,
backgroundColor: UIColor = .white,
correctionLevel: String = "M"
) -> UIImage? {
// First generate the basic QR code
guard let qrCodeImage = generateQRCode(from: string, size: size, correctionLevel: correctionLevel) else {
return nil
}

// Create a new image context with the desired size
UIGraphicsBeginImageContextWithOptions(size, false, 0)
defer { UIGraphicsEndImageContext() }

guard let context = UIGraphicsGetCurrentContext() else {
return nil
}

// Fill the background
backgroundColor.setFill()
context.fill(CGRect(origin: .zero, size: size))

// Draw the QR code with the foreground color
context.setFillColor(foregroundColor.cgColor)
context.setBlendMode(.sourceIn)

// Create a mask from the original QR code
if let cgImage = qrCodeImage.cgImage {
let maskImage = UIImage(cgImage: cgImage)
maskImage.draw(in: CGRect(origin: .zero, size: size))
}

return UIGraphicsGetImageFromCurrentImageContext()
}
}
77 changes: 77 additions & 0 deletions LoopFollow/Helpers/Views/QRCodeDisplayView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// LoopFollow
// QRCodeDisplayView.swift
// Created by codebymini.

import SwiftUI
import UIKit

struct QRCodeDisplayView: View {
let qrCodeString: String
let size: CGSize
let foregroundColor: UIColor
let backgroundColor: UIColor

@State private var qrCodeImage: UIImage?

init(
qrCodeString: String,
size: CGSize = CGSize(width: 250, height: 250),
foregroundColor: UIColor = .black,
backgroundColor: UIColor = .white
) {
self.qrCodeString = qrCodeString
self.size = size
self.foregroundColor = foregroundColor
self.backgroundColor = backgroundColor
}

var body: some View {
VStack(spacing: 16) {
if let qrCodeImage = qrCodeImage {
Image(uiImage: qrCodeImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: size.width, height: size.height)
.cornerRadius(12)
.shadow(radius: 4)
} else {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.frame(width: size.width, height: size.height)
.overlay(
ProgressView()
.scaleEffect(1.5)
)
}

Text("Scan this QR code with another LoopFollow app to import remote command settings")
.font(.caption)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
}
.onAppear {
generateQRCode()
}
}

private func generateQRCode() {
DispatchQueue.global(qos: .userInitiated).async {
let image = QRCodeGenerator.generateQRCode(
from: qrCodeString,
size: size,
foregroundColor: foregroundColor,
backgroundColor: backgroundColor
)

DispatchQueue.main.async {
self.qrCodeImage = image
}
}
}
}

#Preview {
QRCodeDisplayView(qrCodeString: "https://example.com/test")
.padding()
}
1 change: 1 addition & 0 deletions LoopFollow/Log/LogManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class LogManager {
case alarm = "Alarm"
case calendar = "Calendar"
case deviceStatus = "Device Status"
case remote = "Remote"
}

init() {
Expand Down
Loading