diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index af77310..fb33cdd 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7547DF24032CB6004E8406 /* VehiclePhotoCell.swift */; }; 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; }; 7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A220A248D67B60073DFD9 /* VehicleReportImage.swift */; }; + 7A8A220E248EF5830073DFD9 /* SwiftJWT in Frameworks */ = {isa = PBXBuildFile; productRef = 7A8A220D248EF5830073DFD9 /* SwiftJWT */; }; 7A96AE2A246AFD6200297C33 /* Eureka in Frameworks */ = {isa = PBXBuildFile; productRef = 7A96AE29246AFD6200297C33 /* Eureka */; }; 7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */; }; 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; }; @@ -144,6 +145,7 @@ 7A11472123FEA18700B424AF /* RxCocoa in Frameworks */, 7A0516182414EC1200FC55AC /* RxDataSources in Frameworks */, 7A96AE2A246AFD6200297C33 /* Eureka in Frameworks */, + 7A8A220E248EF5830073DFD9 /* SwiftJWT in Frameworks */, 7A051611241412CA00FC55AC /* SwiftDate in Frameworks */, 7A11472323FEA18700B424AF /* RxBlocking in Frameworks */, 7A530B8B240181F500CBFE6E /* RxRealm in Frameworks */, @@ -220,7 +222,6 @@ 7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */, 7A6DD90724329144009DE740 /* CenterTextLayer.swift */, 7A96AE32246C095700297C33 /* Base64FS.swift */, - 7A11474323FF06CA00B424AF /* Api.swift */, ); path = ThirdParty; sourceTree = ""; @@ -230,6 +231,7 @@ children = ( 7A96AE30246B2FE400297C33 /* Constants.swift */, 7A43F9F7246C8A6200BA5B49 /* JWT.swift */, + 7A11474323FF06CA00B424AF /* Api.swift */, ); path = Utils; sourceTree = ""; @@ -356,6 +358,7 @@ 7A0516152414EC1200FC55AC /* Differentiator */, 7A0516172414EC1200FC55AC /* RxDataSources */, 7A96AE29246AFD6200297C33 /* Eureka */, + 7A8A220D248EF5830073DFD9 /* SwiftJWT */, ); productName = AutoCat; productReference = 7A1146FD23FDE7E500B424AF /* AutoCat.app */; @@ -396,6 +399,7 @@ 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */, 7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */, 7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */, + 7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */, ); productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */; projectDirPath = ""; @@ -615,7 +619,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 13; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -637,7 +641,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 13; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -733,6 +737,14 @@ minimumVersion = 2.0.0; }; }; + 7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/IBM-Swift/Swift-JWT.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.6.1; + }; + }; 7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/xmartlabs/Eureka"; @@ -820,6 +832,11 @@ package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */; productName = RxRealm; }; + 7A8A220D248EF5830073DFD9 /* SwiftJWT */ = { + isa = XCSwiftPackageProductDependency; + package = 7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */; + productName = SwiftJWT; + }; 7A96AE29246AFD6200297C33 /* Eureka */ = { isa = XCSwiftPackageProductDependency; package = 7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */; diff --git a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7f46c2f..a2048b0 100644 --- a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,6 +10,33 @@ "version": "4.1.0" } }, + { + "package": "Cryptor", + "repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git", + "state": { + "branch": null, + "revision": "12d2bf3ec7207ec3cd004b9582f69ef5fae1da3b", + "version": "1.0.32" + } + }, + { + "package": "CryptorECC", + "repositoryURL": "https://github.com/IBM-Swift/BlueECC.git", + "state": { + "branch": null, + "revision": "73f362cb0d9c5f1fd0089240d7b293cd2bff18db", + "version": "1.2.5" + } + }, + { + "package": "CryptorRSA", + "repositoryURL": "https://github.com/IBM-Swift/BlueRSA.git", + "state": { + "branch": null, + "revision": "8ea901f2582296837d88f882b0fa5a0601759598", + "version": "1.0.35" + } + }, { "package": "Eureka", "repositoryURL": "https://github.com/xmartlabs/Eureka", @@ -37,6 +64,24 @@ "version": "5.14.0" } }, + { + "package": "KituraContracts", + "repositoryURL": "https://github.com/IBM-Swift/KituraContracts.git", + "state": { + "branch": null, + "revision": "a30e2fb79e926672776a05ec6b919c239870a221", + "version": "1.2.1" + } + }, + { + "package": "LoggerAPI", + "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git", + "state": { + "branch": null, + "revision": "3357dd9526cdf9436fa63bb792b669e6efdc43da", + "version": "1.9.0" + } + }, { "package": "MagazineLayout", "repositoryURL": "https://github.com/airbnb/MagazineLayout", @@ -91,6 +136,24 @@ "version": "5.1.1" } }, + { + "package": "SwiftJWT", + "repositoryURL": "https://github.com/IBM-Swift/Swift-JWT.git", + "state": { + "branch": null, + "revision": "0d435423d12e61c0d14adb6d04396c08a6a650f1", + "version": "3.6.1" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa", + "version": "1.2.0" + } + }, { "package": "SwiftDate", "repositoryURL": "https://github.com/malcommac/SwiftDate.git", diff --git a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 2358118..ca176d8 100644 --- a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -28,7 +28,7 @@ BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> + + + + diff --git a/AutoCat/Base.lproj/Main.storyboard b/AutoCat/Base.lproj/Main.storyboard index 4330343..277d826 100644 --- a/AutoCat/Base.lproj/Main.storyboard +++ b/AutoCat/Base.lproj/Main.storyboard @@ -172,14 +172,23 @@ - - - - - + + + + + + + + + + + + + + @@ -214,14 +223,14 @@ - + - + - + @@ -621,6 +630,7 @@ + diff --git a/AutoCat/Controllers/ReportController.swift b/AutoCat/Controllers/ReportController.swift index 5f33333..f3d7114 100644 --- a/AutoCat/Controllers/ReportController.swift +++ b/AutoCat/Controllers/ReportController.swift @@ -2,6 +2,7 @@ import UIKit import MagazineLayout import Kingfisher import LinkPresentation +import SwiftJWT enum ReportSection: Int, CaseIterable, CustomStringConvertible { case header = 0 @@ -75,9 +76,17 @@ enum ReportEngineSection: Int, CaseIterable, CustomStringConvertible { } } +struct ReportLinkClaims: Claims { + let exp: Date + let iat: Date + let plateNumber: String +} + class ReportController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateMagazineLayout, MediaBrowserViewControllerDataSource, MediaBrowserViewControllerDelegate, UIActivityItemSource { @IBOutlet weak var collection: UICollectionView! + @IBOutlet weak var actionBarItem: UIBarButtonItem! + @IBOutlet weak var copyBarItem: UIBarButtonItem! private let fullWidth = MagazineLayoutItemSizeMode(widthMode: .fullWidth(respectsHorizontalInsets: true), heightMode: .dynamic) private var reportImageUrl: URL? @@ -345,11 +354,11 @@ class ReportController: UIViewController, UICollectionViewDataSource, UICollecti @IBAction func onShare(_ sender: UIBarButtonItem) { guard let vehicle = self.vehicle else { return } - let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - sheet.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem + let sheet = UIAlertController(title: "Share report", message: nil, preferredStyle: .actionSheet) + sheet.popoverPresentationController?.barButtonItem = self.actionBarItem let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) } - let share = UIAlertAction(title: "Share", style: .default) { _ in + let shareImage = UIAlertAction(title: "As one image", style: .default) { _ in let image = vehicle.reportImage(width: self.collection.contentSize.width) do { @@ -368,12 +377,42 @@ class ReportController: UIViewController, UICollectionViewDataSource, UICollecti print(error) } } - let copyPlateNumber = UIAlertAction(title: "Copy plate number", style: .default) { _ in UIPasteboard.general.string = self.vehicle?.number } - let copyVin = UIAlertAction(title: "Copy VIN", style: .default) { _ in UIPasteboard.general.string = self.vehicle?.vin1 } - sheet.addAction(share) - sheet.addAction(copyPlateNumber) - sheet.addAction(copyVin) + let shareTextAndImage = UIAlertAction(title: "As text and photos", style: .default) { _ in + guard let vehicle = self.vehicle else { return } + var items: [Any] = [vehicle.reportText()] + for photo in vehicle.photos { + if let url = URL(string: photo.url) { + if let image = ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { + items.append(image) + } + + } + } + + let controller = UIActivityViewController(activityItems: items, applicationActivities: nil) + controller.popoverPresentationController?.barButtonItem = sender + self.present(controller, animated: true) + } + + let shareLink = UIAlertAction(title: "As link", style: .default) { _ in + guard let vehicle = self.vehicle else { return } + + let iat = Date() + let exp = Date(timeIntervalSinceNow: 24*60*60) + let claims = ReportLinkClaims(exp: exp, iat: iat, plateNumber: vehicle.number) + let signer = JWTSigner.hs256(key: Constants.reportLinkTokenSecret.data(using: .utf8)!) + var jwt = SwiftJWT.JWT(claims: claims) + if let jwtSigned = try? jwt.sign(using: signer), let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwtSigned) { + let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil) + controller.popoverPresentationController?.barButtonItem = sender + self.present(controller, animated: true) + } + } + + sheet.addAction(shareImage) + sheet.addAction(shareTextAndImage) + sheet.addAction(shareLink) sheet.addAction(cancel) self.present(sheet, animated: true, completion: nil) } @@ -397,4 +436,18 @@ class ReportController: UIViewController, UICollectionViewDataSource, UICollecti metadata.iconProvider = NSItemProvider.init(contentsOf: url) return metadata } + + // MARK: - Copy + + @IBAction func onCopy(_ sender: UIBarButtonItem) { + let sheet = UIAlertController(title: "Copy to pasteboard", message: nil, preferredStyle: .actionSheet) + sheet.popoverPresentationController?.barButtonItem = self.copyBarItem + let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) } + let copyPlateNumber = UIAlertAction(title: "Plate number", style: .default) { _ in UIPasteboard.general.string = self.vehicle?.number } + let copyVin = UIAlertAction(title: "VIN", style: .default) { _ in UIPasteboard.general.string = self.vehicle?.vin1 } + sheet.addAction(copyPlateNumber) + sheet.addAction(copyVin) + sheet.addAction(cancel) + self.present(sheet, animated: true, completion: nil) + } } diff --git a/AutoCat/Extensions/VehicleReportImage.swift b/AutoCat/Extensions/VehicleReportImage.swift index cbbae9a..db8d334 100644 --- a/AutoCat/Extensions/VehicleReportImage.swift +++ b/AutoCat/Extensions/VehicleReportImage.swift @@ -64,7 +64,7 @@ extension Vehicle { let w: CGFloat = width var y: CGFloat = 0 - UIColor.systemBackground.setFill() + UIColor.systemGroupedBackground.setFill() UIRectFill(rect) y += 12 @@ -106,7 +106,7 @@ extension Vehicle { y += cellHeight self.drawCell(y: y, width: w, height: cellHeight, title: "Fuel type", value: self.engine?.fuelType ?? "", context: ctx) y += cellHeight - self.drawCell(y: y, width: w, height: cellHeight, title: "Volume", value: String(self.engine?.volume ?? 0), context: ctx) + self.drawCell(y: y, width: w, height: cellHeight, title: "Volume (cm³)", value: String(self.engine?.volume ?? 0), context: ctx) y += cellHeight self.drawCell(y: y, width: w, height: cellHeight, title: "Power (HP)", value: String(self.engine?.powerHp ?? 0), context: ctx) y += cellHeight @@ -172,4 +172,44 @@ extension Vehicle { return image.cutHeight(to: realHeight) } + + func reportText() -> String { + let formatter = DateFormatter() + formatter.dateStyle = .long + formatter.timeStyle = .none + + var text = (self.brand?.name?.original ?? "") + "\n" + text += "Year: \(self.year)\n" + if let color = self.color { text += "Color: \(color)\n" } + if let category = self.category { text += "Category: \(category)\n" } + text += "Steering wheel position: \(self.isRightWheel ? "right" : "left")\n" + text += "Japanese: \(self.isJapanese ? "yes" : "no")\n" + text += "Plate number: \(self.number)\n" + if let vin = self.vin1 { text += "VIN: \(vin)\n" } + if let sts = self.sts { text += "STS: \(sts)\n" } + if let pts = self.pts { text += "PTS: \(pts)\n" } + if let engineNumber = self.engine?.number { text += "Engine number: \(engineNumber)\n" } + if let fuel = self.engine?.fuelType { text += "Fuel type: \(fuel)\n" } + if let volume = self.engine?.volume { text += "Volume (cm³): \(volume)\n" } + if let powerHp = self.engine?.powerHp { text += "Power (HP): \(powerHp)\n" } + if let powerHp = self.engine?.powerHp { text += "Power (HP): \(powerHp)\n" } + + if self.ownershipPeriods.count > 0 { + text += "\n" + text += "Ownership periods: \(self.ownershipPeriods.count)\n" + text += "\n" + for period in self.ownershipPeriods { + text += "Owner type: \(period.ownerType)\n" + let fromDate = Date(timeIntervalSince1970: TimeInterval(period.from/1000)) + text += "From: \(formatter.string(from: fromDate))\n" + let toDate = Date(timeIntervalSince1970: TimeInterval(period.to/1000)) + let toString = period.to == 0 ? "now" : formatter.string(from: toDate) + text += "To: \(toString)\n" + text += "Last operation: \(period.lastOperation)\n" + text += "\n" + } + } + + return text + } } diff --git a/AutoCat/ThirdParty/Api.swift b/AutoCat/Utils/Api.swift similarity index 99% rename from AutoCat/ThirdParty/Api.swift rename to AutoCat/Utils/Api.swift index 078bf3d..5a7ded9 100644 --- a/AutoCat/ThirdParty/Api.swift +++ b/AutoCat/Utils/Api.swift @@ -7,7 +7,7 @@ class Api { } private static func createRequest(api: String, method: String, body: [String: T]? = nil) -> URLRequest? where T: LosslessStringConvertible { - guard var urlComponents = URLComponents(string: Constants.debugBaseUrl + api) else { return nil } + guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil } if let body = body, method.uppercased() == "GET" { urlComponents.queryItems = body.map { URLQueryItem(name: $0, value: String($1)) } diff --git a/AutoCat/Utils/Constants.swift b/AutoCat/Utils/Constants.swift index b7e2ebb..074f9be 100644 --- a/AutoCat/Utils/Constants.swift +++ b/AutoCat/Utils/Constants.swift @@ -1,8 +1,14 @@ import Foundation enum Constants { - static let baseUrl = "https://vps.aliencat.pro:8443/" - static let debugBaseUrl = "http://127.0.0.1:3000/" + static var baseUrl: String { + #if DEBUG + //return "http://127.0.0.1:3000/" + return "https://vps.aliencat.pro:8443/" + #else + return "https://vps.aliencat.pro:8443/" + #endif + } static let googleAuthURL = "https://accounts.google.com/o/oauth2/v2/auth" static let googleTokenURL = "https://oauth2.googleapis.com/token" @@ -17,4 +23,7 @@ enum Constants { static let fbUserAgent = "FirebaseAuth.iOS/6.5.1 ru.Vin01/1.0 iPhone/13.5 hw/sim" static let vin01BUndleId = "ru.Vin01" + + static let reportLinkTokenSecret = "#TheTruthIsOutThere" + static let reportLinkBaseURL = "https://auto.aliencat.pro/report.html" }