Я человек далекий от программирования, живу за колючкой и люблю бить баклуши. Но случилось непредвиденное, моему сербскому другу Джонни, приехавшему в гости, понадобился QR-код. Пришлось расчехлить клавиатуру.
Краткое содержание первой серии
Ко мне приехал мой сербский приятель Никола Павлович, вакцинированный2 Пфайзером. В нашей стране его всюду просили показать QR-код или гуляй мимо. Ситуация идиотская. Человек дважды привит Пфайзером, а его никуда не пускают. Я был в бешенстве.
-Джонни, сгенерю-ка я тебе QR-код, - подумал я и сгенерил.
Как действует QR-код
Чтобы понять сценарий верификации кода, пришлось отправиться в местную Плазу (привет Виктору Левашову) и предъявить охраннику свой QR-сертификат. Откуда у меня сертификат? А я вакцинирован Спутником V. И вот что я обнаружил
охранник включил телефон
охранник включил камеру на телефоне
навел камеру на мой QR-код
нажал на всплывающий поп-ап
Проходи!
На экране телефона охранника высветилась информация обо мне в стыдливо прикрытом виде
Sic! В QR-коде просто зашифрован линк-ссылка на сайт Госуслуг. В чьей-то голове сформулировалось техническое задание о разработке приложения под iOS. Голова оказалась моя. И заметьте, информация на экране приложения не является персональной. Очевидно, что аббревиатура ФИО и 3 последние цифры паспорта никак не идентифицируют человека. По этим данным даже пол гражданина (гражданки?) не определить.
Техническое задание
Надо сделать сайт-пародию (дюжина строк кода на PHP) который отображает информацию из строки url-запроса типа http://govuslugi.her/vam_qr.php?string=BBB_15031964_22345
Надо сделать iOS приложение (пару дюжин строк на swift), которое будет генерить QR-код и показывать его на экране iPhone.
Серверная часть она же Web
Вообще, публикация планировалась как учебник по SwiftUI+MVVM+Reactive, но для общего понимания задачи приведу прототип серверной части.
Я сделал веб-страничку на PHP, она будет отображать входящую информацию, записанную в параметре name нашего url-запроса. Типа www.fig.vam/vsem.php?name=jonny_depp
Я извиняюсь, последний раз я кодировал на пе-хе-пе в 2005 году, поэтому прошу прощения за олд-стайл. Слава Джобсу, в PHP не надо проверять строку на её существование и на наличие в ней символов в нужных местах, код никогда не ломается и это прекрасно. Ой, тухлые помидоры!
if (isset($_GET['name'])) $name=($_GET['name']); else $name="Xerr";
list($fio, $birth, $pass) = explode("_", $name);
$user = m_substr($fio, 0, 1, 'UTF-8')."***** ".m_substr($fio, 1, 1, 'UTF-8')."***** ".mb_substr($fio, 2, 1, 'UTF-8')."********";
$passport = "Паспорт: ".substr($pass, 0, 2)."*****".substr($pass, 2, 3);
$birthday = "Дата рождения: ".$birth;
Как видите, мы получили 3 нужные переменные, которые будем отображать на веб-страничке. П-последний раз я кодировал на Х-Т-М-Л в 1997 году, поэтому извините за олд-олд-стайл из прошлого тысячелетия.
echo '
<div class="top"><strong>
<span style="font-size:140%;color:#FF0000">QR</span>
<span style="font-size:140%;color:#6666CC">УСЛУГИ</span></strong>
<span style="color:#ffffff">BASHNI.ORG</span>
<span style="font-size:140%;color:#000000">RUS</span></div>
<div class="relative">СЕРТИФИКАТ О ГЕНЕРАЦИИ<br>QR-КОДА-19
<div class="absolute">Действителен</div>
<br><p>№ QR9290499831</p></div><div class="info"><br>'.$user.'</div>
<div class="info">'.$port.'</div>
<div class="info">Дата рождения: '.$birth.'</div>
<div class="close">Закрыть</div>';
Стили я опустил, их можно посмотреть на гитхабе. Они тупые и загадочные, как <див>ы из древних сказок Востока.
SwiftUI+MVVM
Переходим к приложению под iOS. Прости, google. Выбираем паттерн проектирования MVVM. Разумеется, для нашей маленькой задачи он избыточен, но потренироваться всегда полезно. Вдруг интервью на сеньора?? Я готов, если что, звоните от $4000. Телефон зашит в статье.
Модель содержит простейшую структуру из строки, которую надо где-то превратить в код-картинку и логическую переменную, говорящую что превращение совершилось/не совершилось.
struct UserModel {
var user: User
mutating func updateUser(name:String, birth:String, pass:String) {
let d = "_"
let temp = name+d+birth+d+pass
self.user = User(isValid: true, string: temp )
}
mutating func cleanUser() {
self.user = User(isValid: false, string: "")
}
struct User {
var isValid:Bool
var string:String
func genUrl()->String {
return urlPath+self.string
}
}
}
Полный текст файла ModelView.swift можно посмотреть на гитхабе. Вторая часть нашего паттерна - файл ContentView.swift. Он рисует в одном окне InputView ввод строки и кнопки, а во втором окне QRView изображение QR-кода. Этот SwiftUI стиль и он очень информативен - не нужны XIB-ы, верстка идет прямо в коде в джава-стиле.
import SwiftUI
struct ContentView: View {
@ObservedObject var model: ModelView
var body: some View {
if model.isValid() {
QRView(model: model)
} else {
InputView(model: model)
}
}
}
struct InputView: View {
var model: ModelView
@State private var name: String = ""
@State private var birth: String = ""
@State private var passport: String = ""
var body: some View {
VStack {
Spacer()
TextField("3 буквы ФИО", text: $name)
TextField("ДР 15.03.1964", text: $birth)
TextField("2+3 цифры паспорта", text: $passport)
Spacer()
HStack {
Button("Сделай QR", action: {
self.model.tapQRButton(name, birth, passport)
})
Spacer()
Button("Очистить", action: {
name=""
birth = ""
passport = ""
self.model.tapClean()
})
}.padding()
Spacer()
}
.padding()
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
struct QRView: View {
var model: ModelView
@State private var isSharePresented: Bool = false
var body: some View {
VStack {
if let image = model.generateQRCode() {
Text("QR код").bold()
Text("успешно сформирован")
Spacer()
Image(uiImage: image).resizable()
.frame(width: 200, height: 200)
} else {
Text("QR код").bold().foregroundColor(.red)
Text("не сформирован").foregroundColor(.red)
Spacer()
Image(systemName: "qrcode").resizable()
.frame(width: 75, height: 75)
}
Spacer()
Button("Назад", action: { self.model.tapClean()
} ).padding()
}.padding()
Spacer()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(model: ModelView())
}
}
И, наконец, последний акт Мерлезонского балета - это файл ModelView.swift, который одним глазом глядит на ContentView и получает от него сообщения о нажатии на кнопку, а другим глазом он глядит на UserModel, куда он складывает полученную от ContentView информацию и забирает строку, которую превращает в QR-картинку и возвращает в ContentView.
//
// ModelView.swift
//
import UIKit
class ModelView: ObservableObject {
@Published private var model:UserModel = UserModel()
func isValid()->Bool {
return model.user.isValid
}
func tapQRButton(_ name:String,_ birth:String,_ pass:String) {
model.updateUser(name: name, birth: birth, pass: pass)
}
func tapClean() {
model.cleanUser()
}
func generateQRCode() -> UIImage? {
var uiImage: UIImage?
// let string = model.user.genUrl()
let string = "https://github.com/PapaBubaDiop/qrushka"
if let data = string.data(using: String.Encoding.utf8) {
if let filter = CIFilter(name: "CIQRCodeGenerator",
parameters: ["inputMessage": data,
"inputCorrectionLevel": "L"]) {
if let outputImage = filter.outputImage,
let cgImage = CIContext().createCGImage(outputImage,
from: outputImage.extent) {
let size = CGSize(width: outputImage.extent.width * 5.0,
height: outputImage.extent.height * 5.0)
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
context.interpolationQuality = .none
context.draw(cgImage,
in: CGRect(origin: .zero,
size: size))
uiImage = UIGraphicsGetImageFromCurrentImageContext()
}
UIGraphicsEndImageContext()
}
}
}
return uiImage
}
}
ModelView объявлена внутри ContentView реактивной
@ObservedObject var model: ModelView
и это значит, что графические окна перерисовываются каждый раз когда меняется содержимое нашей модели. Еще раз сценарий нашего приложения
вводим текст и нажимаем кнопку в ContentView
ModelView получает этот текст и обновляет UserModel и значит меняется сам
UserModel формирует из текста url виде строки
раз UserModel изменился, то и ModelView изменился, значит ContentView перерисовывается в соотвествии с новыми данными, которые появились в ModelView
Ура, проект готов и компилируется. No Errors No Warnings.
Запускаем приложение в симуляторе
И получаем стартовый экран. Заполняем поля в соответсвии с документом Джонни.
Нажимаем кнопку Сделай QR и получаем QR-картинку, в которой скрыт линк на наш сайт, где уже лежит php-скрипт, описанный выше. Сформированные картинки, граждане, настоящие!
Вы можете навести свой девайс на картинку и протестировать ее. Мою протестировал тот же охранник из Плазы. Welcome to Russia, Джонни!
Внимание! Никогда так не делайте! Не используйте прототип в незаконных целях! Потому что скоро это будет весить от 3-х до 5-ти.
Заключение
Вся эта история, разумеется, вымышлена от начала и до конца, но код, товарищи, настоящий! Ссылка на гитхаб прилагается.
Задачка
QR-код формируется разной зернистости в зависимости от длины строки. Мое приложение зашифровало в коды число 0 и число 1. Угадайте, где какой QR-код для 0 и 1?