Resolve conflicts
This commit is contained in:
commit
08c27510ce
51 changed files with 2442 additions and 66 deletions
55
.gitignore
vendored
55
.gitignore
vendored
|
@ -79,3 +79,58 @@ Dependencies/
|
|||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
# Xcode & macOS
|
||||
|
||||
# OS X temporary files that should never be committed
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.lock
|
||||
profile
|
||||
|
||||
# Xcode temporary files that should never be committed
|
||||
*~.nib
|
||||
|
||||
# Xcode build files
|
||||
DerivedData/
|
||||
build/
|
||||
.build/
|
||||
|
||||
# Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups)
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
.perspectivev3
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
xcuserdata/**/
|
||||
|
||||
# Cocoapods: cocoapods.org
|
||||
Pods/
|
||||
Podfile.lock
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
Packages/
|
||||
Package.pins
|
||||
Package.resolved
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/.png
|
||||
fastlane/test_output
|
5
sdk/.gitignore
vendored
Normal file
5
sdk/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
28
sdk/Package.swift
Normal file
28
sdk/Package.swift
Normal file
|
@ -0,0 +1,28 @@
|
|||
// swift-tools-version:5.3
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Sdk",
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "Sdk",
|
||||
targets: ["Sdk"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "Sdk",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "SdkTests",
|
||||
dependencies: ["Sdk"]),
|
||||
]
|
||||
)
|
3
sdk/README.md
Normal file
3
sdk/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Sdk
|
||||
|
||||
A description of this package.
|
27
sdk/Sources/Sdk/APIError.swift
Normal file
27
sdk/Sources/Sdk/APIError.swift
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public extension Sdk {
|
||||
enum APIError: Error {
|
||||
case error(reason: String)
|
||||
case jsonSerialization
|
||||
case noEndpointURL
|
||||
case noFirebaseToken
|
||||
case noCertificate
|
||||
case noPrivateKey
|
||||
case noSignatureValues
|
||||
case urlError
|
||||
|
||||
case wrongToken
|
||||
case wrongSymbol
|
||||
case wrongPin
|
||||
}
|
||||
}
|
||||
|
21
sdk/Sources/Sdk/Extensions/Date.swift
Normal file
21
sdk/Sources/Sdk/Extensions/Date.swift
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
extension Date {
|
||||
func formattedString(_ format: String) -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = format
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
|
||||
var millisecondsSince1970: Int64 {
|
||||
Int64((self.timeIntervalSince1970 * 1000.0).rounded())
|
||||
}
|
||||
}
|
70
sdk/Sources/Sdk/Extensions/URLRequest.swift
Normal file
70
sdk/Sources/Sdk/Extensions/URLRequest.swift
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 15/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
extension URLRequest {
|
||||
func signed(with certificate: X509) throws -> URLRequest {
|
||||
// Create request
|
||||
var request = self
|
||||
|
||||
// Signing stuff
|
||||
guard let urlString = request.url?.absoluteString else {
|
||||
throw Sdk.APIError.urlError
|
||||
}
|
||||
|
||||
// Get private key
|
||||
guard let privateKeyRawData = certificate.getPrivateKeyData(format: .DER),
|
||||
let privateKeyString = String(data: privateKeyRawData, encoding: .utf8)?
|
||||
.split(separator: "\n")
|
||||
.dropFirst()
|
||||
.dropLast()
|
||||
.joined()
|
||||
.data(using: .utf8) else {
|
||||
throw Sdk.APIError.noPrivateKey
|
||||
}
|
||||
|
||||
// Create SecKey
|
||||
let attributes = [
|
||||
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||
]
|
||||
guard let privateKeyData = Data(base64Encoded: privateKeyString),
|
||||
let secKey = SecKeyCreateWithData(privateKeyData as NSData, attributes as NSDictionary, nil) else {
|
||||
throw Sdk.APIError.noPrivateKey
|
||||
}
|
||||
|
||||
// Get fingerprint
|
||||
guard let signatureValues = Sdk.Signer.getSignatureValues(body: request.httpBody, url: urlString, privateKey: secKey, fingerprint: certificate.getCertificateFingerprint().lowercased()) else {
|
||||
throw Sdk.APIError.noSignatureValues
|
||||
}
|
||||
|
||||
let now = Date()
|
||||
var vDate: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
return "\(dateFormatter.string(from: now)) GMT"
|
||||
}
|
||||
|
||||
// Headers
|
||||
request.setValue("iOS", forHTTPHeaderField: "vOS")
|
||||
request.setValue("1", forHTTPHeaderField: "vAPI")
|
||||
request.setValue(vDate, forHTTPHeaderField: "vDate")
|
||||
request.setValue(signatureValues.canonicalURL, forHTTPHeaderField: "vCanonicalUrl")
|
||||
request.setValue(signatureValues.signature, forHTTPHeaderField: "Signature")
|
||||
|
||||
if let digest = signatureValues.digest {
|
||||
request.setValue("SHA-256=\(digest)", forHTTPHeaderField: "Digest")
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
}
|
50
sdk/Sources/Sdk/Extensions/getSignatures.swift
Normal file
50
sdk/Sources/Sdk/Extensions/getSignatures.swift
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// getSignatures.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz on 02/03/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
func getSignatures(request: URLRequest, certificate: X509) -> String {
|
||||
guard let urlString = request.url?.absoluteString else {
|
||||
return "\(Sdk.APIError.urlError)"
|
||||
}
|
||||
|
||||
// Get private key
|
||||
guard let privateKeyRawData = certificate.getPrivateKeyData(format: .DER),
|
||||
let privateKeyString = String(data: privateKeyRawData, encoding: .utf8)?
|
||||
.split(separator: "\n")
|
||||
.dropFirst()
|
||||
.dropLast()
|
||||
.joined()
|
||||
.data(using: .utf8) else {
|
||||
return "\(Sdk.APIError.noPrivateKey)"
|
||||
}
|
||||
|
||||
// Create SecKey
|
||||
let attributes = [
|
||||
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||
]
|
||||
guard let privateKeyData = Data(base64Encoded: privateKeyString),
|
||||
let secKey = SecKeyCreateWithData(privateKeyData as NSData, attributes as NSDictionary, nil) else {
|
||||
return "\(Sdk.APIError.noPrivateKey)"
|
||||
}
|
||||
|
||||
// Get fingerprint
|
||||
guard let signatureValues = Sdk.Signer.getSignatureValues(body: request.httpBody, url: urlString, privateKey: secKey, fingerprint: certificate.getCertificateFingerprint().lowercased()) else {
|
||||
return "\(Sdk.APIError.noPrivateKey)"
|
||||
}
|
||||
|
||||
// Headers
|
||||
let keychain = Keychain()
|
||||
let fingerprint: String! = keychain["keyFingerprint"]
|
||||
|
||||
let signature = "\(signatureValues.signature.replacingOccurrences(of: "nil", with: fingerprint))"
|
||||
|
||||
return signature
|
||||
}
|
293
sdk/Sources/Sdk/Sdk.swift
Normal file
293
sdk/Sources/Sdk/Sdk.swift
Normal file
|
@ -0,0 +1,293 @@
|
|||
//
|
||||
// Sdk.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import os
|
||||
import KeychainAccess
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public class Sdk {
|
||||
static private let libraryVersion: String = "0.0.1"
|
||||
|
||||
private let loggerSubsystem: String = "com.wulkanowy-ios.Sdk"
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
var firebaseToken: String!
|
||||
var endpointURL: String!
|
||||
|
||||
public let certificate: X509
|
||||
|
||||
// MARK: - Init
|
||||
public init(certificate: X509) {
|
||||
self.certificate = certificate
|
||||
}
|
||||
|
||||
// MARK: - Public functions
|
||||
|
||||
/// Logs in with supplied login data.
|
||||
/// - Parameters:
|
||||
/// - token: Login token
|
||||
/// - symbol: Login symbol
|
||||
/// - pin: Login PIN
|
||||
/// - deviceName: Name of the device
|
||||
/// - completionHandler: Callback
|
||||
public func login(token: String, symbol: String, pin: String, deviceModel: String, completionHandler: @escaping (Error?) -> Void) {
|
||||
let logger: Logger = Logger(subsystem: self.loggerSubsystem, category: "Login")
|
||||
logger.debug("Logging in...")
|
||||
|
||||
let endpointPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "http://komponenty.vulcan.net.pl/UonetPlusMobile/RoutingRules.txt")!)
|
||||
.mapError { $0 as Error }
|
||||
|
||||
// Firebase request
|
||||
var firebaseRequest: URLRequest = URLRequest(url: URL(string: "https://android.googleapis.com/checkin")!)
|
||||
firebaseRequest.httpMethod = "POST"
|
||||
firebaseRequest.setValue("application/json", forHTTPHeaderField: "Content-type")
|
||||
firebaseRequest.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
|
||||
|
||||
let firebaseRequestBody: [String: Any] = [
|
||||
"locale": "pl_PL",
|
||||
"digest": "",
|
||||
"checkin": [
|
||||
"iosbuild": [
|
||||
"model": deviceModel,
|
||||
"os_version": Self.libraryVersion
|
||||
],
|
||||
"last_checkin_msec": 0,
|
||||
"user_number": 0,
|
||||
"type": 2
|
||||
],
|
||||
"time_zone": TimeZone.current.identifier,
|
||||
"user_serial_number": 0,
|
||||
"id": 0,
|
||||
"logging_id": 0,
|
||||
"version": 2,
|
||||
"security_token": 0,
|
||||
"fragment": 0
|
||||
]
|
||||
firebaseRequest.httpBody = try? JSONSerialization.data(withJSONObject: firebaseRequestBody)
|
||||
|
||||
let firebasePublisher = URLSession.shared.dataTaskPublisher(for: firebaseRequest)
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
.tryCompactMap { value -> AnyPublisher<Data, Error> in
|
||||
guard let dictionary: [String: Any] = try? JSONSerialization.jsonObject(with: value.data) as? [String: Any] else {
|
||||
throw APIError.jsonSerialization
|
||||
}
|
||||
|
||||
var request: URLRequest = URLRequest(url: URL(string: "https://fcmtoken.googleapis.com/register")!)
|
||||
request.httpMethod = "POST"
|
||||
request.setValue("AidLogin \(dictionary["android_id"] as? Int ?? 0):\(dictionary["security_token"] as? Int ?? 0)", forHTTPHeaderField: "Authorization")
|
||||
request.setValue("gzip", forHTTPHeaderField: "Accept-Encoding")
|
||||
|
||||
let body: String = "device=\(dictionary["android_id"] as? Int ?? 0)&app=pl.edu.vulcan.hebe&sender=987828170337&X-subtype=987828170337&appid=dLIDwhjvE58&gmp_app_id=1:987828170337:ios:6b65a4ad435fba7f"
|
||||
request.httpBody = body.data(using: .utf8)
|
||||
|
||||
return URLSession.shared.dataTaskPublisher(for: request)
|
||||
.receive(on: DispatchQueue.global(qos: .background))
|
||||
.mapError { $0 as Error }
|
||||
.map { $0.data }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.flatMap { $0 }
|
||||
|
||||
Publishers.Zip(endpointPublisher, firebasePublisher)
|
||||
.tryMap { (endpoints, firebaseToken) -> (String, String) in
|
||||
// Find endpointURL
|
||||
let lines = String(data: endpoints.data, encoding: .utf8)?.split { $0.isNewline }
|
||||
|
||||
var endpointURL: String?
|
||||
lines?.forEach { line in
|
||||
let items = line.split(separator: ",")
|
||||
if (token.starts(with: items[0])) {
|
||||
endpointURL = String(items[1])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let finalEndpointURL: String = endpointURL else {
|
||||
throw APIError.noEndpointURL
|
||||
}
|
||||
|
||||
// Get Firebase token
|
||||
guard let token: String = String(data: firebaseToken, encoding: .utf8)?.components(separatedBy: "token=").last else {
|
||||
logger.error("Token empty! Response: \"\(firebaseToken.base64EncodedString(), privacy: .private)\"")
|
||||
throw APIError.noFirebaseToken
|
||||
}
|
||||
|
||||
return (finalEndpointURL, token)
|
||||
}
|
||||
.tryMap { endpointURL, firebaseToken in
|
||||
try self.registerDevice(endpointURL: endpointURL, firebaseToken: firebaseToken, token: token, symbol: symbol, pin: pin, deviceModel: deviceModel)
|
||||
.mapError { $0 as Error }
|
||||
.map { $0.data }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.flatMap { $0 }
|
||||
.sink(receiveCompletion: { completion in
|
||||
switch completion {
|
||||
case .finished:
|
||||
break
|
||||
case .failure(let error):
|
||||
completionHandler(error)
|
||||
}
|
||||
}, receiveValue: { data in
|
||||
if let response = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let parsedError = self.parseResponse(response) {
|
||||
completionHandler(parsedError)
|
||||
} else {
|
||||
self.getStudents(symbol: symbol, deviceModel: deviceModel)
|
||||
completionHandler(nil)
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
/// Registers the device
|
||||
/// - Parameters:
|
||||
/// - endpointURL: API endpoint URL
|
||||
/// - firebaseToken: FCM token
|
||||
/// - token: Vulcan token
|
||||
/// - symbol: Vulcan symbol
|
||||
/// - pin: Vulcan PIN
|
||||
/// - deviceModel: Device model
|
||||
/// - Throws: Error
|
||||
/// - Returns: URLSession.DataTaskPublisher
|
||||
private func registerDevice(endpointURL: String, firebaseToken: String, token: String, symbol: String, pin: String, deviceModel: String) throws -> URLSession.DataTaskPublisher {
|
||||
self.endpointURL = endpointURL
|
||||
self.firebaseToken = firebaseToken
|
||||
guard let keyFingerprint = certificate.getPrivateKeyFingerprint(format: .PEM)?.replacingOccurrences(of: ":", with: "").lowercased(),
|
||||
let keyData = certificate.getPublicKeyData(),
|
||||
let keyBase64 = String(data: keyData, encoding: .utf8)?
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Join
|
||||
else {
|
||||
throw APIError.noCertificate
|
||||
}
|
||||
|
||||
// Request
|
||||
let url = "\(endpointURL)/\(symbol)/api/mobile/register/new"
|
||||
var request = URLRequest(url: URL(string: url)!)
|
||||
request.httpMethod = "POST"
|
||||
|
||||
let now: Date = Date()
|
||||
var timestampFormatted: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
|
||||
return dateFormatter.string(from: now)
|
||||
}
|
||||
|
||||
let keychain = Keychain()
|
||||
keychain[string: "keyFingerprint"] = keyFingerprint
|
||||
|
||||
// Body
|
||||
let body: [String: Encodable?] = [
|
||||
"AppName": "DzienniczekPlus 2.0",
|
||||
"AppVersion": Self.libraryVersion,
|
||||
"CertificateId": nil,
|
||||
"Envelope": [
|
||||
"OS": "iOS",
|
||||
"PIN": pin,
|
||||
"Certificate": keyBase64,
|
||||
"CertificateType": "RSA_PEM",
|
||||
"DeviceModel": deviceModel,
|
||||
"SecurityToken": token,
|
||||
"SelfIdentifier": UUID().uuidString.lowercased(),
|
||||
"CertificateThumbprint": keyFingerprint
|
||||
],
|
||||
"FirebaseToken": firebaseToken,
|
||||
"API": 1,
|
||||
"RequestId": UUID().uuidString.lowercased(),
|
||||
"Timestamp": now.millisecondsSince1970,
|
||||
"TimestampFormatted": "\(timestampFormatted) GMT"
|
||||
]
|
||||
|
||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
|
||||
|
||||
request.allHTTPHeaderFields = [
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"vDeviceModel": deviceModel
|
||||
]
|
||||
|
||||
let signedRequest = try request.signed(with: certificate)
|
||||
|
||||
return URLSession.shared.dataTaskPublisher(for: signedRequest)
|
||||
}
|
||||
|
||||
private func getStudents(symbol: String, deviceModel: String) {
|
||||
let url = "\(self.endpointURL!)/\(symbol)/api/mobile/register/hebe"
|
||||
var request = URLRequest(url: URL(string: url)!)
|
||||
request.httpMethod = "GET"
|
||||
|
||||
let now = Date()
|
||||
var vDate: String {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
return "\(dateFormatter.string(from: now)) GMT"
|
||||
}
|
||||
|
||||
let signatures = getSignatures(request: request, certificate: certificate)
|
||||
request.setValue("\(signatures)", forHTTPHeaderField: "Signature")
|
||||
|
||||
request.allHTTPHeaderFields = [
|
||||
"User-Agent": "wulkanowy/1 CFNetwork/1220.1 Darwin/20.1.0",
|
||||
"vOS": "iOS",
|
||||
"vDeviceModel": deviceModel,
|
||||
"vAPI": "1",
|
||||
"vDate": vDate,
|
||||
"vCanonicalUrl": "api%2fmobile%2fregister%2fhebe"
|
||||
]
|
||||
|
||||
let session = URLSession.shared
|
||||
let task = session.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
// Handle HTTP request error
|
||||
print(error)
|
||||
} else if let data = data {
|
||||
// Handle HTTP request response
|
||||
print(String(data: data, encoding: String.Encoding.utf8) as Any)
|
||||
} else {
|
||||
// Handle unexpected error
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Helper functions
|
||||
|
||||
/// Parses the response
|
||||
/// - Parameter response: Request response
|
||||
/// - Returns: VulcanKit.APIError?
|
||||
private func parseResponse(_ response: [String: Any]) -> APIError? {
|
||||
guard let status = response["Status"] as? [String: Any],
|
||||
let statusCode = status["Code"] as? Int else {
|
||||
return nil
|
||||
}
|
||||
|
||||
print("Response status code: \(statusCode)")
|
||||
|
||||
switch statusCode {
|
||||
case 0: return nil
|
||||
case 200: return APIError.wrongToken
|
||||
case -1: return APIError.wrongSymbol //Ya, Vulcan returns -1 code
|
||||
case 203: return APIError.wrongPin
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
192
sdk/Sources/Sdk/X509.swift
Normal file
192
sdk/Sources/Sdk/X509.swift
Normal file
|
@ -0,0 +1,192 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import OpenSSL
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public class X509: ObservableObject {
|
||||
enum X509Error: Error {
|
||||
case errorGeneratingPKEY
|
||||
}
|
||||
|
||||
public enum KeyFormat {
|
||||
case PEM
|
||||
case DER
|
||||
}
|
||||
|
||||
let certificate: OpaquePointer
|
||||
let pkey: OpaquePointer
|
||||
|
||||
public init(serialNumber: Int, certificateEntries: [String: String]) throws {
|
||||
let x509: OpaquePointer = X509_new()
|
||||
|
||||
// serial number
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x509), serialNumber)
|
||||
|
||||
// version
|
||||
X509_set_version(x509, 0x2) // v3
|
||||
|
||||
// validity date
|
||||
X509_gmtime_adj(X509_getm_notBefore(x509), 0)
|
||||
X509_gmtime_adj(X509_getm_notAfter(x509), 60 * 60 * 24 * 365 * 10) // 60 seconds * 60 minutes * 24 hours * 365 days * 10 years
|
||||
|
||||
// key
|
||||
guard let pkey = EVP_PKEY_new() else {
|
||||
throw X509Error.errorGeneratingPKEY
|
||||
}
|
||||
|
||||
let exponent = BN_new()
|
||||
BN_set_word(exponent, 0x10001)
|
||||
|
||||
let rsa = RSA_new()
|
||||
RSA_generate_key_ex(rsa, 2048, exponent, nil)
|
||||
EVP_PKEY_set1_RSA(pkey, rsa)
|
||||
|
||||
X509_set_pubkey(x509, pkey)
|
||||
self.pkey = pkey
|
||||
|
||||
// issuer
|
||||
let subjectName = X509_get_subject_name(x509)
|
||||
for (key, value) in certificateEntries {
|
||||
X509_NAME_add_entry_by_txt(subjectName, key, MBSTRING_ASC, value, -1, -1, 0)
|
||||
}
|
||||
|
||||
X509_set_issuer_name(x509, subjectName)
|
||||
|
||||
// sign the certificate
|
||||
X509_sign(x509, pkey, EVP_sha256())
|
||||
|
||||
self.certificate = x509
|
||||
}
|
||||
|
||||
/// Gets the private key used to sign the certificate data.
|
||||
/// - Parameter format: Format of the returned key
|
||||
/// - Returns: Private key data
|
||||
public func getPrivateKeyData(format: KeyFormat) -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
|
||||
switch format {
|
||||
case .PEM: PEM_write_bio_PrivateKey(bio, self.pkey, nil, nil, 0, nil, nil)
|
||||
case .DER: PEM_write_bio_PrivateKey_traditional(bio, self.pkey, nil, nil, 0, nil, nil)
|
||||
}
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Gets the public key used to sign the certificate data.
|
||||
/// - Returns: Public key data
|
||||
public func getPublicKeyData() -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
PEM_write_bio_PUBKEY(bio, self.pkey)
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Gets the generated certificate data.
|
||||
/// - Returns: Certificate data
|
||||
public func getCertificateData() -> Data? {
|
||||
let bio = BIO_new(BIO_s_mem())
|
||||
PEM_write_bio_X509(bio, self.certificate)
|
||||
|
||||
var pointer: UnsafeMutableRawPointer?
|
||||
let len = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &pointer)
|
||||
|
||||
guard let nonEmptyPointer = pointer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let data = Data(bytes: nonEmptyPointer, count: len)
|
||||
BIO_vfree(bio)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// Get certificate thumbrint.
|
||||
/// - Returns: Certificate fingerprint
|
||||
public func getCertificateFingerprint() -> String {
|
||||
let md: UnsafeMutablePointer<UInt8> = .allocate(capacity: Int(EVP_MAX_MD_SIZE))
|
||||
var n: UInt32 = 0
|
||||
|
||||
X509_digest(self.certificate, EVP_sha1(), md, &n)
|
||||
return UnsafeMutableBufferPointer(start: md, count: Int(EVP_MAX_MD_SIZE))
|
||||
.prefix(Int(n))
|
||||
.makeIterator()
|
||||
.map {
|
||||
let string = String($0, radix: 16)
|
||||
return ($0 < 16 ? "0" + string : string)
|
||||
}
|
||||
.joined(separator: ":")
|
||||
.uppercased()
|
||||
}
|
||||
|
||||
/// Get public key fingerprint
|
||||
/// - Returns: Public key fingerprint
|
||||
public func getPublicKeyFingerprint() -> String? {
|
||||
guard let keyData = self.getPublicKeyData(),
|
||||
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let keyB64 = rawKeyB64
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Combine
|
||||
|
||||
guard let data = Data(base64Encoded: keyB64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hash = Insecure.MD5.hash(data: data)
|
||||
return hash.map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
|
||||
/// Get private key fingerprint
|
||||
/// - Parameter format: Format of the returned key
|
||||
/// - Returns: Private key fingerprint
|
||||
public func getPrivateKeyFingerprint(format: KeyFormat) -> String? {
|
||||
guard let keyData = self.getPrivateKeyData(format: format),
|
||||
let rawKeyB64 = String(data: keyData, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let keyB64 = rawKeyB64
|
||||
.split(separator: "\n") // Split by newline
|
||||
.dropFirst() // Drop prefix
|
||||
.dropLast() // Drop suffix
|
||||
.joined() // Combine
|
||||
|
||||
guard let data = Data(base64Encoded: keyB64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hash = Insecure.MD5.hash(data: data)
|
||||
return hash.map { String(format: "%02hhx", $0) }.joined()
|
||||
}
|
||||
}
|
12
sdk/Sources/Sdk/data/luckyNumber.swift
Normal file
12
sdk/Sources/Sdk/data/luckyNumber.swift
Normal file
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// luckyNumber.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz on 27/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public func getLuckyNumber() -> Int {
|
||||
return 7
|
||||
}
|
98
sdk/Sources/Sdk/signer.swift
Normal file
98
sdk/Sources/Sdk/signer.swift
Normal file
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 14/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import KeychainAccess
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
public extension Sdk {
|
||||
struct Signer {
|
||||
static public func getSignatureValues(body: Data?, url: String, date: Date = Date(), privateKey: SecKey, fingerprint: String) -> (digest: String?, canonicalURL: String, signature: String)? {
|
||||
// Canonical URL
|
||||
guard let canonicalURL = getCanonicalURL(url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Digest
|
||||
let digest: String?
|
||||
if let body = body {
|
||||
digest = Data(SHA256.hash(data: body)).base64EncodedString()
|
||||
} else {
|
||||
digest = nil
|
||||
}
|
||||
|
||||
// Headers & values
|
||||
let headersList = getHeadersList(digest: digest, canonicalURL: canonicalURL, date: date)
|
||||
|
||||
// Signature value
|
||||
guard let data = headersList.values.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let signatureData = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA256, data as CFData, nil) as Data?
|
||||
guard let signatureValue = signatureData?.base64EncodedString() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (
|
||||
digest,
|
||||
canonicalURL,
|
||||
"keyId=\"\(fingerprint.replacingOccurrences(of: ":", with: ""))\",headers=\"\(headersList.headers)\",algorithm=\"sha256withrsa\",signature=Base64(SHA256withRSA(\(signatureValue)))"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
/// Finds and encodes the first canonical URL match in the supplied URL.
|
||||
/// - Parameter url: URL to find matches in
|
||||
/// - Returns: Canonical URL
|
||||
static internal func getCanonicalURL(_ url: String) -> String? {
|
||||
guard let regex = try? NSRegularExpression(pattern: "(api/mobile/.+)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let results = regex.matches(in: url, range: NSRange(url.startIndex..., in: url))
|
||||
return results.compactMap {
|
||||
guard let range = Range($0.range, in: url) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(url[range]).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)?.lowercased()
|
||||
}
|
||||
.first
|
||||
}
|
||||
|
||||
/// Creates a tuple with formatted headers and values needed to sign the request.
|
||||
/// - Parameters:
|
||||
/// - body: Body of the request
|
||||
/// - digest: Digest of the request
|
||||
/// - canonicalURL: Canonical URL of the request
|
||||
/// - date: Date of the request
|
||||
/// - Returns: Formatted headers and values
|
||||
static internal func getHeadersList(digest: String?, canonicalURL: String, date: Date) -> (headers: String, values: String) {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss"
|
||||
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
|
||||
let dateString = "\(dateFormatter.string(from: date)) GMT"
|
||||
|
||||
let signData: [(key: String, value: String)] = [
|
||||
("vCanonicalUrl", canonicalURL),
|
||||
digest == nil ? nil : ("Digest", digest ?? ""),
|
||||
("vDate", "\(dateString)")
|
||||
]
|
||||
.compactMap { $0 }
|
||||
|
||||
let headers = signData.map(\.key).joined(separator: " ")
|
||||
let values = signData.map(\.value).joined()
|
||||
|
||||
return (headers, values)
|
||||
}
|
||||
}
|
||||
}
|
7
sdk/Tests/LinuxMain.swift
Normal file
7
sdk/Tests/LinuxMain.swift
Normal file
|
@ -0,0 +1,7 @@
|
|||
import XCTest
|
||||
|
||||
import SdkTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += SdkTests.allTests()
|
||||
XCTMain(tests)
|
15
sdk/Tests/SdkTests/SdkTests.swift
Normal file
15
sdk/Tests/SdkTests/SdkTests.swift
Normal file
|
@ -0,0 +1,15 @@
|
|||
import XCTest
|
||||
@testable import Sdk
|
||||
|
||||
final class SdkTests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(Sdk().text, "Hello, World!")
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testExample", testExample),
|
||||
]
|
||||
}
|
9
sdk/Tests/SdkTests/XCTestManifests.swift
Normal file
9
sdk/Tests/SdkTests/XCTestManifests.swift
Normal file
|
@ -0,0 +1,9 @@
|
|||
import XCTest
|
||||
|
||||
#if !canImport(ObjectiveC)
|
||||
public func allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(SdkTests.allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
|
@ -3,16 +3,30 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 52;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
967B5B9825D813F5006ED944 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967B5B9725D813F5006ED944 /* LoginView.swift */; };
|
||||
96A2D96325D6FEA6001CB109 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96A2D96525D6FEA6001CB109 /* Localizable.strings */; };
|
||||
96A2D97B25D7003F001CB109 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A2D97A25D7003F001CB109 /* OnboardingView.swift */; };
|
||||
96A2D98825D73DCF001CB109 /* CustomButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A2D98725D73DCF001CB109 /* CustomButtonView.swift */; };
|
||||
96A5571725D81BD20094BF48 /* CustomTextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A5571625D81BD20094BF48 /* CustomTextFieldView.swift */; };
|
||||
5C1794B425E8FDFB007AD91A /* messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1794B325E8FDFB007AD91A /* messages.swift */; };
|
||||
5C1794B825E8FE08007AD91A /* notes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1794B725E8FE08007AD91A /* notes.swift */; };
|
||||
5C1794BC25E8FE19007AD91A /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1794BB25E8FE19007AD91A /* settings.swift */; };
|
||||
5C1794C025E8FE27007AD91A /* about.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1794BF25E8FE27007AD91A /* about.swift */; };
|
||||
5C1794CD25E90DBD007AD91A /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 5C1794CC25E90DBD007AD91A /* KeychainAccess */; };
|
||||
5C1CFA7A25EA32AE0047286F /* ghImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1CFA7925EA32AE0047286F /* ghImage.swift */; };
|
||||
5C2D331025E64F3C000253AC /* grades.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2D330F25E64F3C000253AC /* grades.swift */; };
|
||||
5C2D331425E650EC000253AC /* exams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2D331325E650EC000253AC /* exams.swift */; };
|
||||
5C2D331825E651C4000253AC /* homework.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2D331725E651C4000253AC /* homework.swift */; };
|
||||
5C2D331C25E651FB000253AC /* more.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2D331B25E651FB000253AC /* more.swift */; };
|
||||
5C478F3525DC742100ABEFB7 /* VulcanStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C478F3425DC742100ABEFB7 /* VulcanStore.swift */; };
|
||||
5C89C8F525EA6AA4000B5816 /* licenses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C89C8F425EA6AA4000B5816 /* licenses.swift */; };
|
||||
5C89C90625EA7996000B5816 /* SwiftUIEKtensions in Frameworks */ = {isa = PBXBuildFile; productRef = 5C89C90525EA7996000B5816 /* SwiftUIEKtensions */; };
|
||||
5C9B6F4925D6C08D00C3F5F5 /* Sdk in Frameworks */ = {isa = PBXBuildFile; productRef = 5C9B6F4825D6C08D00C3F5F5 /* Sdk */; };
|
||||
5CC2EAA525E516F100B6183E /* dashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC2EAA425E516F100B6183E /* dashboard.swift */; };
|
||||
5CC2EAAE25E526B500B6183E /* navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC2EAAD25E526B500B6183E /* navigation.swift */; };
|
||||
5CCAE31625DA4CDD00D87580 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 5CCAE31525DA4CDD00D87580 /* OpenSSL */; };
|
||||
5CEA516B25D540B900DB45BD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CEA516D25D540B900DB45BD /* Localizable.strings */; };
|
||||
F4C6D9082544E17400F8903A /* wulkanowyApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C6D9072544E17400F8903A /* wulkanowyApp.swift */; };
|
||||
F4C6D90A2544E17400F8903A /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C6D9092544E17400F8903A /* LoginView.swift */; };
|
||||
F4C6D90C2544E17500F8903A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4C6D90B2544E17500F8903A /* Assets.xcassets */; };
|
||||
F4C6D90F2544E17500F8903A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4C6D90E2544E17500F8903A /* Preview Assets.xcassets */; };
|
||||
F4C6D91A2544E17500F8903A /* wulkanowyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C6D9192544E17500F8903A /* wulkanowyTests.swift */; };
|
||||
|
@ -36,15 +50,50 @@
|
|||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5C1F6D5F25D6891300AFDDD6 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C9B6EEC25D6B25200C3F5F5 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
967B5B9725D813F5006ED944 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
96A2D96425D6FEA6001CB109 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
96A2D96925D6FEBB001CB109 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
96A2D97A25D7003F001CB109 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
96A2D98725D73DCF001CB109 /* CustomButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonView.swift; sourceTree = "<group>"; };
|
||||
96A5571625D81BD20094BF48 /* CustomTextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextFieldView.swift; sourceTree = "<group>"; };
|
||||
5C1794B325E8FDFB007AD91A /* messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = messages.swift; sourceTree = "<group>"; };
|
||||
5C1794B725E8FE08007AD91A /* notes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notes.swift; sourceTree = "<group>"; };
|
||||
5C1794BB25E8FE19007AD91A /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = "<group>"; };
|
||||
5C1794BF25E8FE27007AD91A /* about.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = about.swift; sourceTree = "<group>"; };
|
||||
5C1CFA7925EA32AE0047286F /* ghImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ghImage.swift; sourceTree = "<group>"; };
|
||||
5C2D330F25E64F3C000253AC /* grades.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = grades.swift; sourceTree = "<group>"; };
|
||||
5C2D331325E650EC000253AC /* exams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = exams.swift; sourceTree = "<group>"; };
|
||||
5C2D331725E651C4000253AC /* homework.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = homework.swift; sourceTree = "<group>"; };
|
||||
5C2D331B25E651FB000253AC /* more.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = more.swift; sourceTree = "<group>"; };
|
||||
5C478F3425DC742100ABEFB7 /* VulcanStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VulcanStore.swift; sourceTree = "<group>"; };
|
||||
5C89C8F425EA6AA4000B5816 /* licenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = licenses.swift; sourceTree = "<group>"; };
|
||||
5C9B6E4925D6ADFB00C3F5F5 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
5C9B6F4525D6C06D00C3F5F5 /* Sdk */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Sdk; sourceTree = "<group>"; };
|
||||
5CC2EAA425E516F100B6183E /* dashboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dashboard.swift; sourceTree = "<group>"; };
|
||||
5CC2EAAD25E526B500B6183E /* navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = navigation.swift; sourceTree = "<group>"; };
|
||||
5CEA516C25D540B900DB45BD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CF81BD725D9D44400B12C4C /* pl-PL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pl-PL"; path = "pl-PL.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
F4C6D9042544E17400F8903A /* wulkanowy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wulkanowy.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F4C6D9072544E17400F8903A /* wulkanowyApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = wulkanowyApp.swift; sourceTree = "<group>"; };
|
||||
F4C6D9092544E17400F8903A /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
F4C6D90B2544E17500F8903A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
F4C6D90E2544E17500F8903A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
F4C6D9102544E17500F8903A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -61,6 +110,10 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C9B6F4925D6C08D00C3F5F5 /* Sdk in Frameworks */,
|
||||
5CCAE31625DA4CDD00D87580 /* OpenSSL in Frameworks */,
|
||||
5C1794CD25E90DBD007AD91A /* KeychainAccess in Frameworks */,
|
||||
5C89C90625EA7996000B5816 /* SwiftUIEKtensions in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -81,16 +134,7 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
967B5B9625D813E2006ED944 /* Login */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
967B5B9725D813F5006ED944 /* LoginView.swift */,
|
||||
96A5571625D81BD20094BF48 /* CustomTextFieldView.swift */,
|
||||
);
|
||||
path = Login;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
96A2D95625D6FE4D001CB109 /* App */ = {
|
||||
5C1B848625E1B6740074F29D /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F4C6D9072544E17400F8903A /* wulkanowyApp.swift */,
|
||||
|
@ -98,46 +142,74 @@
|
|||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
96A2D95A25D6FE81001CB109 /* Resources */ = {
|
||||
5C1B848925E1B6910074F29D /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
96A2D96525D6FEA6001CB109 /* Localizable.strings */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
96A2D96D25D6FF29001CB109 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
96A5571B25D859460094BF48 /* Shared */,
|
||||
967B5B9625D813E2006ED944 /* Login */,
|
||||
96A2D97925D7002D001CB109 /* Onboarding */,
|
||||
5CC2EAAC25E5269E00B6183E /* Navigation */,
|
||||
5CC2EAA325E516DD00B6183E /* Content */,
|
||||
5C1B849F25E1B7A30074F29D /* Login */,
|
||||
5C1CFA7925EA32AE0047286F /* ghImage.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
96A2D97925D7002D001CB109 /* Onboarding */ = {
|
||||
5C1B848E25E1B6FA0074F29D /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
96A2D97A25D7003F001CB109 /* OnboardingView.swift */,
|
||||
5CEA516D25D540B900DB45BD /* Localizable.strings */,
|
||||
);
|
||||
path = Onboarding;
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
96A5571B25D859460094BF48 /* Shared */ = {
|
||||
5C1B849F25E1B7A30074F29D /* Login */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
96A2D98725D73DCF001CB109 /* CustomButtonView.swift */,
|
||||
F4C6D9092544E17400F8903A /* LoginView.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
path = Login;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5C9B6E4825D6ADFB00C3F5F5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C9B6E4925D6ADFB00C3F5F5 /* NetworkExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CC2EAA325E516DD00B6183E /* Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CC2EAA425E516F100B6183E /* dashboard.swift */,
|
||||
5C2D330F25E64F3C000253AC /* grades.swift */,
|
||||
5C2D331325E650EC000253AC /* exams.swift */,
|
||||
5C2D331725E651C4000253AC /* homework.swift */,
|
||||
5C2D331B25E651FB000253AC /* more.swift */,
|
||||
5C1794B325E8FDFB007AD91A /* messages.swift */,
|
||||
5C1794B725E8FE08007AD91A /* notes.swift */,
|
||||
5C1794BB25E8FE19007AD91A /* settings.swift */,
|
||||
5C1794BF25E8FE27007AD91A /* about.swift */,
|
||||
5C89C8F425EA6AA4000B5816 /* licenses.swift */,
|
||||
);
|
||||
path = Content;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CC2EAAC25E5269E00B6183E /* Navigation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CC2EAAD25E526B500B6183E /* navigation.swift */,
|
||||
);
|
||||
path = Navigation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F4C6D8FB2544E17300F8903A = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C9B6F4525D6C06D00C3F5F5 /* Sdk */,
|
||||
F4C6D9062544E17400F8903A /* wulkanowy */,
|
||||
F4C6D9182544E17500F8903A /* wulkanowyTests */,
|
||||
F4C6D9232544E17500F8903A /* wulkanowyUITests */,
|
||||
5C9B6E4825D6ADFB00C3F5F5 /* Frameworks */,
|
||||
F4C6D9052544E17400F8903A /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
|
@ -155,12 +227,13 @@
|
|||
F4C6D9062544E17400F8903A /* wulkanowy */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
96A2D96D25D6FF29001CB109 /* Views */,
|
||||
96A2D95A25D6FE81001CB109 /* Resources */,
|
||||
96A2D95625D6FE4D001CB109 /* App */,
|
||||
5C1B848E25E1B6FA0074F29D /* Resources */,
|
||||
5C1B848925E1B6910074F29D /* Views */,
|
||||
5C1B848625E1B6740074F29D /* App */,
|
||||
F4C6D90B2544E17500F8903A /* Assets.xcassets */,
|
||||
F4C6D9102544E17500F8903A /* Info.plist */,
|
||||
F4C6D90D2544E17500F8903A /* Preview Content */,
|
||||
5C478F3425DC742100ABEFB7 /* VulcanStore.swift */,
|
||||
);
|
||||
path = wulkanowy;
|
||||
sourceTree = "<group>";
|
||||
|
@ -201,12 +274,20 @@
|
|||
F4C6D9002544E17300F8903A /* Sources */,
|
||||
F4C6D9012544E17300F8903A /* Frameworks */,
|
||||
F4C6D9022544E17300F8903A /* Resources */,
|
||||
5C1F6D5F25D6891300AFDDD6 /* Embed App Extensions */,
|
||||
5C9B6EEC25D6B25200C3F5F5 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = wulkanowy;
|
||||
packageProductDependencies = (
|
||||
5C9B6F4825D6C08D00C3F5F5 /* Sdk */,
|
||||
5CCAE31525DA4CDD00D87580 /* OpenSSL */,
|
||||
5C1794CC25E90DBD007AD91A /* KeychainAccess */,
|
||||
5C89C90525EA7996000B5816 /* SwiftUIEKtensions */,
|
||||
);
|
||||
productName = wulkanowy;
|
||||
productReference = F4C6D9042544E17400F8903A /* wulkanowy.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
@ -253,7 +334,7 @@
|
|||
F4C6D8FC2544E17300F8903A /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1210;
|
||||
LastSwiftUpdateCheck = 1240;
|
||||
LastUpgradeCheck = 1210;
|
||||
TargetAttributes = {
|
||||
F4C6D9032544E17300F8903A = {
|
||||
|
@ -276,9 +357,14 @@
|
|||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
pl,
|
||||
"pl-PL",
|
||||
);
|
||||
mainGroup = F4C6D8FB2544E17300F8903A;
|
||||
packageReferences = (
|
||||
5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */,
|
||||
5C1794CB25E90DBD007AD91A /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
5C89C90425EA7996000B5816 /* XCRemoteSwiftPackageReference "SwiftUIEKtensions" */,
|
||||
);
|
||||
productRefGroup = F4C6D9052544E17400F8903A /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
@ -296,7 +382,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F4C6D90F2544E17500F8903A /* Preview Assets.xcassets in Resources */,
|
||||
96A2D96325D6FEA6001CB109 /* Localizable.strings in Resources */,
|
||||
5CEA516B25D540B900DB45BD /* Localizable.strings in Resources */,
|
||||
F4C6D90C2544E17500F8903A /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -362,11 +448,11 @@
|
|||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
96A2D96525D6FEA6001CB109 /* Localizable.strings */ = {
|
||||
5CEA516D25D540B900DB45BD /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
96A2D96425D6FEA6001CB109 /* en */,
|
||||
96A2D96925D6FEBB001CB109 /* pl */,
|
||||
5CEA516C25D540B900DB45BD /* en */,
|
||||
5CF81BD725D9D44400B12C4C /* pl-PL */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
|
@ -426,7 +512,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -482,7 +568,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -499,10 +585,10 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"wulkanowy/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GTH776WFJL;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = wulkanowy/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -521,10 +607,10 @@
|
|||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"wulkanowy/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GTH776WFJL;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
INFOPLIST_FILE = wulkanowy/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
@ -656,6 +742,55 @@
|
|||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
5C1794CB25E90DBD007AD91A /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 4.2.1;
|
||||
};
|
||||
};
|
||||
5C89C90425EA7996000B5816 /* XCRemoteSwiftPackageReference "SwiftUIEKtensions" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/EnesKaraosman/SwiftUIEKtensions";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.1.8;
|
||||
};
|
||||
};
|
||||
5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/krzyzanowskim/OpenSSL";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.1.180;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
5C1794CC25E90DBD007AD91A /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5C1794CB25E90DBD007AD91A /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
productName = KeychainAccess;
|
||||
};
|
||||
5C89C90525EA7996000B5816 /* SwiftUIEKtensions */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5C89C90425EA7996000B5816 /* XCRemoteSwiftPackageReference "SwiftUIEKtensions" */;
|
||||
productName = SwiftUIEKtensions;
|
||||
};
|
||||
5C9B6F4825D6C08D00C3F5F5 /* Sdk */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Sdk;
|
||||
};
|
||||
5CCAE31525DA4CDD00D87580 /* OpenSSL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5CCAE31025DA4CCA00D87580 /* XCRemoteSwiftPackageReference "OpenSSL" */;
|
||||
productName = OpenSSL;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = F4C6D8FC2544E17300F8903A /* Project object */;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
10
wulkanowy.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
wulkanowy.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:wulkanowy.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -6,12 +6,14 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import Sdk
|
||||
import Combine
|
||||
|
||||
@main
|
||||
struct wulkanowyApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
LoginView()
|
||||
NavigationBarView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x09",
|
||||
"green" : "0x02",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -37,7 +37,11 @@
|
|||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
"size" : "40x40",
|
||||
=======
|
||||
"filename" : "logo.jpg",
|
||||
>>>>>>> feature/register-device
|
||||
"idiom" : "iphone",
|
||||
"filename" : "wulkanowy-40@2x.png",
|
||||
"scale" : "2x"
|
||||
|
|
BIN
wulkanowy/Assets.xcassets/AppIcon.appiconset/logo.jpg
Normal file
BIN
wulkanowy/Assets.xcassets/AppIcon.appiconset/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.184",
|
||||
"green" : "0.184",
|
||||
"red" : "0.827"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2E",
|
||||
"red" : "0xD2"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
9
wulkanowy/Assets.xcassets/Colours/Contents.json
Normal file
9
wulkanowy/Assets.xcassets/Colours/Contents.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"compression-type" : "automatic"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2E",
|
||||
"red" : "0xD2"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x2E",
|
||||
"green" : "0x2E",
|
||||
"red" : "0xD2"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"platform" : "ios",
|
||||
"reference" : "darkTextColor"
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000",
|
||||
"red" : "1.000"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
21
wulkanowy/Assets.xcassets/Logo.imageset/Contents.json
vendored
Normal file
21
wulkanowy/Assets.xcassets/Logo.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "logo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
wulkanowy/Assets.xcassets/Logo.imageset/logo.png
vendored
Normal file
BIN
wulkanowy/Assets.xcassets/Logo.imageset/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
24
wulkanowy/Assets.xcassets/splash.imageset/Contents.json
vendored
Normal file
24
wulkanowy/Assets.xcassets/splash.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wulkanowy-logo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"compression-type" : "automatic"
|
||||
}
|
||||
}
|
BIN
wulkanowy/Assets.xcassets/splash.imageset/wulkanowy-logo.png
vendored
Normal file
BIN
wulkanowy/Assets.xcassets/splash.imageset/wulkanowy-logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
24
wulkanowy/Assets.xcassets/wulkanowy.imageset/Contents.json
vendored
Normal file
24
wulkanowy/Assets.xcassets/wulkanowy.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wulkanowy.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "original"
|
||||
}
|
||||
}
|
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy.svg
vendored
Normal file
9
wulkanowy/Assets.xcassets/wulkanowy.imageset/wulkanowy.svg
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024px" height="1024px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Shape</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="wulkanowy" fill="#000000" fill-rule="nonzero">
|
||||
<path d="M295.17362,965.05417 C296.24282,968.52944 295.70822,972.27203 293.83692,975.34631 L268.03972,1017.1831 C265.50012,1021.3267 260.82192,1024 255.74272,1024 L14.345318,1024 C3.1176178,1024 -3.6991822,1012.2376 2.3157178,1003.4158 L157.76692,774.44928 C158.70252,773.11265 159.23732,771.64234 159.63822,770.17205 L230.88102,465.95272 C231.68312,462.34379 234.08912,459.26952 237.56432,457.39823 L334.06972,404.46727 C337.54502,402.59597 339.95092,399.5217 340.75292,395.77911 L353.71832,339.23923 C356.39162,327.47681 373.23342,324.93718 379.91642,335.22932 L384.59472,342.71451 C386.59962,345.92244 387.13432,349.93236 385.79782,353.54129 L297.84672,607.77024 C297.17842,609.77521 296.91122,611.91384 297.31212,614.05247 L319.23302,735.68673 C319.63402,738.09268 319.36642,740.63229 318.29732,742.90458 L266.03482,860.26158 C264.83182,863.06854 264.56442,866.14281 265.50012,868.94975 L295.17362,965.05417 Z M1009.7413,1024 L843.46322,1024 C838.65152,1024 834.24042,1021.5941 831.56732,1017.8515 L719.69042,860.52891 C719.02212,859.45961 718.35382,858.3903 717.95292,857.18731 L662.48222,695.18653 C661.41302,691.9786 658.87342,689.17164 655.66532,687.56767 L519.86272,618.99802 C516.12012,617.12675 513.44682,613.65147 512.64482,609.77521 L492.59532,510.32918 C492.32792,508.72522 491.65962,507.12125 490.59032,505.65094 L444.47622,437.88328 C441.93662,434.14069 441.53572,429.59611 443.40692,425.58618 L471.47632,365.57105 C473.61502,361.02648 478.29332,357.95219 483.63972,357.68486 L535.76872,354.61059 C538.84292,354.47689 541.64992,353.40763 543.92232,351.53632 L582.28382,321.72925 C589.50162,316.11537 600.46222,318.52131 604.33842,326.40749 L736.53212,595.3395 C737.06672,596.54247 737.46782,597.74545 737.73502,598.94844 L754.04222,707.08262 C754.44312,709.62225 755.51232,711.89452 757.25012,713.76582 L1020.5682,1001.9454 C1028.3207,1010.4999 1021.7712,1024 1009.7413,1024 L1009.7413,1024 Z M363.20822,182.58501 C363.20822,151.97594 382.58972,125.64413 410.39192,113.34703 C408.38692,110.00543 407.18402,106.39651 407.18402,102.52025 C407.18402,87.683545 424.29302,75.653785 445.27822,75.653785 L446.74862,75.653785 C455.43662,61.218065 472.01102,51.460605 490.99122,51.460605 C492.32792,51.460605 493.66452,51.460605 495.00122,51.594305 C496.73892,51.727995 498.47652,50.925975 499.41212,49.589335 C513.44682,28.203095 549.00152,12.965395 590.57112,12.965395 C605.94232,12.965395 620.51172,15.104025 633.47712,18.712955 C636.55152,13.633715 643.36822,10.158455 651.25442,10.158455 C660.21002,10.158455 667.82882,14.703035 670.10102,20.985235 C681.06162,8.287145 699.64082,-4.99999999e-06 720.75972,-4.99999999e-06 C754.44312,-4.99999999e-06 781.71052,21.252565 781.71052,47.450715 C781.71052,50.658645 781.30952,53.732915 780.50752,56.807185 C779.97292,58.945825 781.17582,61.084435 783.44822,61.886425 C804.96812,69.772605 819.53752,84.742975 819.53752,101.85196 C819.53752,121.36691 800.69092,138.07492 774.09172,144.62445 C771.95302,145.15911 770.61642,147.0304 770.61642,149.03537 L770.61642,149.43635 C770.61642,164.54039 755.64602,176.97115 736.39842,178.30779 C736.53172,178.97612 736.53172,179.64442 736.53172,180.44641 C736.53172,209.4515 681.32862,232.84271 613.29352,232.84271 C598.59062,232.84271 584.55582,231.77339 571.59042,229.76844 L571.59042,230.43676 C571.59042,242.46651 556.08532,252.22399 537.10502,252.22399 C536.03582,252.22399 535.10012,252.22399 534.16452,252.0903 C535.50122,255.0309 536.16952,258.10517 536.16952,261.31311 C536.16952,280.02607 512.51092,295.1301 483.23842,295.1301 C480.03052,295.1301 476.95632,294.9964 473.88212,294.59544 C471.47602,294.32813 469.20382,295.79843 468.66902,298.07073 C466.12942,307.4272 457.97602,314.24406 448.21862,314.24406 C436.45612,314.24406 427.09962,304.21926 427.09962,291.92217 C427.09962,286.17462 429.10462,280.96172 432.44622,277.08546 C433.64922,275.74883 434.05012,273.87753 433.24812,272.27355 C431.37682,268.79829 430.57482,265.18936 430.57482,261.31311 C430.57482,259.17449 428.83722,257.43685 426.69852,256.90221 C390.47572,248.88236 363.20822,218.67429 363.20822,182.58501 L363.20822,182.58501 Z M670.10102,908.64795 C670.63582,910.25193 670.76932,911.85591 670.76932,913.59353 L663.01682,1011.5693 C662.48222,1018.5198 656.33362,1024 648.84852,1024 L398.62952,1024 C393.28292,1024 388.33742,1020.9257 385.93132,1016.5148 L344.62922,939.79168 C344.36192,939.39068 344.22822,938.98969 344.09452,938.5887 L305.19832,844.35557 C303.72812,841.01397 303.99532,837.13772 305.59942,833.92976 L370.15902,707.61727 C371.76302,704.543 372.03022,700.93407 370.82732,697.72613 L339.28262,610.30987 C338.21332,607.23559 338.34702,603.76032 339.68372,600.81972 L392.34732,488.6756 C397.69372,477.44782 415.20382,478.38348 418.94642,490.14591 L435.38712,541.20556 L485.51112,675.40424 C486.84782,679.14684 490.05572,682.08744 494.06562,683.42409 L600.99682,719.91436 C605.14032,721.38468 608.34842,724.45894 609.68492,728.3352 L670.10102,908.64795 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -22,6 +22,21 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>komponenty.vulcan.net.pl</key>
|
||||
<dict>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionMinimumTLSVersion</key>
|
||||
<string>TLSv1.1</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
|
@ -30,7 +45,12 @@
|
|||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>UIColorName</key>
|
||||
<string>LaunchColor</string>
|
||||
<key>UIImageName</key>
|
||||
<string>splash</string>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
|
@ -40,6 +60,7 @@
|
|||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
|
|
|
@ -2,17 +2,46 @@
|
|||
Localizable.strings
|
||||
wulkanowy
|
||||
|
||||
Created by Karol Zientek on 12/02/2021.
|
||||
Created by Tomasz on 11/02/2021.
|
||||
|
||||
*/
|
||||
|
||||
"title" = "Wulkanowy";
|
||||
//LOGIN SCREEN
|
||||
"loginTitle" = "Log In";
|
||||
"token" = "Token";
|
||||
"symbol" = "Symbol";
|
||||
"pin" = "Pin";
|
||||
"deviceName" = "Device name";
|
||||
"loginButton" = "Login";
|
||||
"wrongToken" = "Wrong token";
|
||||
"wrongSymbol" = "Wrong symbol";
|
||||
"wrongPin" = "Wrong pin";
|
||||
"invalidData" = "Wrong token, symbol or pin";
|
||||
"success" = "Success";
|
||||
|
||||
"onboarding.description.title" = "Keep track of your educational progress with Wulkanowy app!";
|
||||
"onboarding.description.content" = "Wulkanowy allows you to view your grades, attendance, messages from teachers and many more!";
|
||||
"onboarding.continue" = "Continue";
|
||||
//NAVIGATION
|
||||
"dashboardButton" = "Dashboard";
|
||||
"gradesButton" = "Grades";
|
||||
"examsButton" = "Exams";
|
||||
"homeworkButton" = "Homework";
|
||||
"moreButton" = "More";
|
||||
|
||||
"login.login" = "Log-in";
|
||||
"login.token" = "Token";
|
||||
"login.symbol" = "Symbol";
|
||||
"login.pin" = "PIN";
|
||||
//MORE
|
||||
"messagesButton" = "Messages";
|
||||
"notesButton" = "Notes and achievements";
|
||||
"settingsButton" = "Settings";
|
||||
"aboutButton" = "About";
|
||||
|
||||
//ABOUT
|
||||
"appVersion" = "App version";
|
||||
"appVersionContent" = "You actually version is alpha 0.1";
|
||||
"contributors" = "Contributors";
|
||||
"licensesButton" = "Licenses";
|
||||
"privacyPolicy" = "Privacy policy";
|
||||
"discordButton" = "Join the Discord serwer";
|
||||
"fbButton" = "Facebook fanpage";
|
||||
"reportBug" = "Report a bug";
|
||||
"homepage" = "Homepage";
|
||||
|
||||
//LICENCES
|
||||
"noLicence" = "No licence";
|
||||
|
|
47
wulkanowy/Resources/pl-PL.lproj/Localizable.strings
Normal file
47
wulkanowy/Resources/pl-PL.lproj/Localizable.strings
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
wulkanowy
|
||||
|
||||
Created by Tomasz on 11/02/2021.
|
||||
|
||||
*/
|
||||
|
||||
//LOGIN SCREEN
|
||||
"loginTitle" = "Logowanie";
|
||||
"token" = "Token";
|
||||
"symbol" = "Symbol";
|
||||
"pin" = "Pin";
|
||||
"deviceName" = "Nazwa urządzenia";
|
||||
"loginButton" = "Zaloguj";
|
||||
"wrongToken" = "Zły token";
|
||||
"wrongSymbol" = "Zły symbol";
|
||||
"wrongPin" = "Zły pin";
|
||||
"invalidData" = "Zły token, symbol lub pin";
|
||||
"success" = "Sukces";
|
||||
|
||||
//NAVIGATION
|
||||
"dashboardButton" = "Start";
|
||||
"gradesButton" = "Oceny";
|
||||
"examsButton" = "Sprawdziany";
|
||||
"homeworkButton" = "Zadania";
|
||||
"moreButton" = "Więcej";
|
||||
|
||||
//MORE
|
||||
"messagesButton" = "Wiadomości";
|
||||
"notesButton" = "Uwagi i osiągnięcia";
|
||||
"settingsButton" = "Ustawienia";
|
||||
"aboutButton" = "O aplikacji";
|
||||
|
||||
//ABOUT
|
||||
"appVersion" = "Wersja aplikacji";
|
||||
"appVersionContent" = "Twoja aktualna wersja aplikacji to alpha 0.1";
|
||||
"contributors" = "Twórcy";
|
||||
"licensesButton" = "Licencje";
|
||||
"privacyPolicy" = "Polityka prywatności";
|
||||
"discordButton" = "Dołącz do serwera Discord";
|
||||
"fbButton" = "Fanpage na Facebooku";
|
||||
"reportBug" = "Zgłoś błąd";
|
||||
"homepage" = "Strona domowa";
|
||||
|
||||
//LICENCES
|
||||
"noLicence" = "Brak licencji";
|
121
wulkanowy/Views/Content/about.swift
Normal file
121
wulkanowy/Views/Content/about.swift
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// about.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 26/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Combine
|
||||
import MessageUI
|
||||
import SwiftUIEKtensions
|
||||
|
||||
struct AboutView: View {
|
||||
@State private var result: Result<MFMailComposeResult, Error>? = nil
|
||||
@State private var isShowingMailView = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
DisclosureGroup("appVersion") {
|
||||
Text("appVersionContent")
|
||||
.font(.system(.body, design: .monospaced))
|
||||
}
|
||||
|
||||
DisclosureGroup("contributors") {
|
||||
HStack {
|
||||
AsyncImage(url: URL(string: "https://avatars.githubusercontent.com/u/55411338?s=460&v=4")!,
|
||||
placeholder: { Image(systemName: "circle.dashed") },
|
||||
image: { Image(uiImage: $0).resizable() })
|
||||
.frame(width: 38, height: 38)
|
||||
Link("Pengwius", destination: URL(string: "https://github.com/pengwius")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
|
||||
HStack {
|
||||
AsyncImage(url: URL(string: "https://avatars.githubusercontent.com/u/23171377?s=460&u=ce615ffdaaea96b191b1c27fb915fd18d25eaebd&v=4")!,
|
||||
placeholder: { Image(systemName: "circle.dashed") },
|
||||
image: { Image(uiImage: $0).resizable() })
|
||||
.frame(width: 38, height: 38)
|
||||
Link("rrroyal", destination: URL(string: "https://github.com/rrroyal")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
|
||||
HStack {
|
||||
AsyncImage(url: URL(string: "https://avatars.githubusercontent.com/u/20373275?s=400&u=a59e3ca4656a7113a0021682b6733c27e6742e73&v=4")!,
|
||||
placeholder: { Image(systemName: "circle.dashed") },
|
||||
image: { Image(uiImage: $0).resizable() })
|
||||
.frame(width: 38, height: 38)
|
||||
Link("Karol Z.", destination: URL(string: "https://github.com/szakes1")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(destination: LicensesView()) {
|
||||
Text("licensesButton")
|
||||
}
|
||||
|
||||
Link("FAQ", destination: URL(string: "https://wulkanowy.github.io/czesto-zadawane-pytania")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
|
||||
Link("privacyPolicy", destination: URL(string: "https://wulkanowy.github.io/polityka-prywatnosci")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
Section {
|
||||
Link("discordButton", destination: URL(string: "https://discord.com/invite/vccAQBr")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
|
||||
Link("fbButton", destination: URL(string: "https://www.facebook.com/wulkanowy")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
|
||||
Link("Reddit", destination: URL(string: "https://www.reddit.com/r/wulkanowy/")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
|
||||
Section {
|
||||
Button(action: {
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
self.isShowingMailView.toggle()
|
||||
} else {
|
||||
print("Can't send emails from this device")
|
||||
}
|
||||
if result != nil {
|
||||
print("Result: \(String(describing: result))")
|
||||
}
|
||||
}) {
|
||||
HStack {
|
||||
Text("reportBug")
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
}
|
||||
// .disabled(!MFMailComposeViewController.canSendMail())
|
||||
}
|
||||
.sheet(isPresented: $isShowingMailView) {
|
||||
MailView(result: $result) { composer in
|
||||
composer.setSubject("")
|
||||
composer.setToRecipients(["wulkanowyinc@gmail.com"])
|
||||
}
|
||||
}
|
||||
|
||||
Link("homepage", destination: URL(string: "https://wulkanowy.github.io/")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
|
||||
Link("Github", destination: URL(string: "https://github.com/wulkanowy/wulkanowy-ios")!)
|
||||
.foregroundColor(Color("customControlColor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct AboutView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
AboutView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
44
wulkanowy/Views/Content/dashboard.swift
Normal file
44
wulkanowy/Views/Content/dashboard.swift
Normal file
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Dashboard.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 23/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import KeychainAccess
|
||||
import Sdk
|
||||
|
||||
struct DashboardView: View {
|
||||
init() {
|
||||
let keychain = Keychain()
|
||||
let key = keychain["privateKey"]
|
||||
|
||||
let luckyNumber = getLuckyNumber()
|
||||
print(luckyNumber)
|
||||
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (dashboard)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct DashboardView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
DashboardView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
32
wulkanowy/Views/Content/exams.swift
Normal file
32
wulkanowy/Views/Content/exams.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// attendance.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 24/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ExamsView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (exams)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct ExamsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
ExamsView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
32
wulkanowy/Views/Content/grades.swift
Normal file
32
wulkanowy/Views/Content/grades.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// grades.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 24/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct GradesView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (grades)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct GradesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
GradesView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
32
wulkanowy/Views/Content/homework.swift
Normal file
32
wulkanowy/Views/Content/homework.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// homework.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 24/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HomeworksView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (homeworks)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct HomeworksView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
HomeworksView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
190
wulkanowy/Views/Content/licenses.swift
Normal file
190
wulkanowy/Views/Content/licenses.swift
Normal file
|
@ -0,0 +1,190 @@
|
|||
//
|
||||
// licenses.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 27/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LicensesView: View {
|
||||
let KeychainAccessLicense: String = "The MIT License (MIT)\nCopyright (c) 2014 kishikawa katsumi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
let KeychainAccessURL: URL? = URL(string: "https://github.com/kishikawakatsumi/KeychainAccess")!
|
||||
|
||||
let OpenSSLURL: URL? = URL(string: "https://github.com/krzyzanowskim/OpenSSL")!
|
||||
|
||||
let SwiftUIEKtensionsURL: URL? = URL(string: "https://github.com/EnesKaraosman/SwiftUIEKtensions")!
|
||||
|
||||
let OpenSSLLicense: String = """
|
||||
LICENSE ISSUES
|
||||
==============
|
||||
|
||||
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
|
||||
the OpenSSL License and the original SSLeay license apply to the toolkit.
|
||||
See below for the actual license texts. Actually both licenses are BSD-style
|
||||
Open Source licenses. In case of any license issues related to OpenSSL
|
||||
please contact openssl-core@openssl.org.
|
||||
|
||||
OpenSSL License
|
||||
---------------
|
||||
|
||||
/* ====================================================================
|
||||
* Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. All advertising materials mentioning features or use of this
|
||||
* software must display the following acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
|
||||
*
|
||||
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
|
||||
* endorse or promote products derived from this software without
|
||||
* prior written permission. For written permission, please contact
|
||||
* openssl-core@openssl.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "OpenSSL"
|
||||
* nor may "OpenSSL" appear in their names without prior written
|
||||
* permission of the OpenSSL Project.
|
||||
*
|
||||
* 6. Redistributions of any form whatsoever must retain the following
|
||||
* acknowledgment:
|
||||
* "This product includes software developed by the OpenSSL Project
|
||||
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
|
||||
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This product includes cryptographic software written by Eric Young
|
||||
* (eay@cryptsoft.com). This product includes software written by Tim
|
||||
* Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
*/
|
||||
|
||||
Original SSLeay License
|
||||
-----------------------
|
||||
|
||||
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* This package is an SSL implementation written
|
||||
* by Eric Young (eay@cryptsoft.com).
|
||||
* The implementation was written so as to conform with Netscapes SSL.
|
||||
*
|
||||
* This library is free for commercial and non-commercial use as long as
|
||||
* the following conditions are aheared to. The following conditions
|
||||
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||
* included with this distribution is covered by the same copyright terms
|
||||
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||
* the code are not to be removed.
|
||||
* If this package is used in a product, Eric Young should be given attribution
|
||||
* as the author of the parts of the library used.
|
||||
* This can be in the form of a textual message at program startup or
|
||||
* in documentation (online or textual) provided with the package.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* "This product includes cryptographic software written by
|
||||
* Eric Young (eay@cryptsoft.com)"
|
||||
* The word 'cryptographic' can be left out if the rouines from the library
|
||||
* being used are not cryptographic related :-).
|
||||
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||
* the apps directory (application code) you must include an acknowledgement:
|
||||
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* The licence and distribution terms for any publically available version or
|
||||
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||
* copied and put under another distribution licence
|
||||
* [including the GNU Public Licence.]
|
||||
*/
|
||||
"""
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
// KeychainAccess
|
||||
DisclosureGroup("KeychainAccess") {
|
||||
Text(KeychainAccessLicense)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.onTapGesture {
|
||||
guard let url = KeychainAccessURL else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
|
||||
// OpenSSL
|
||||
DisclosureGroup("OpenSSL") {
|
||||
Text(OpenSSLLicense)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.onTapGesture {
|
||||
guard let url = OpenSSLURL else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
|
||||
// SwiftUIEKtensions
|
||||
DisclosureGroup("SwiftUIEKtensions") {
|
||||
Text("noLicence")
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.onTapGesture {
|
||||
guard let url = SwiftUIEKtensionsURL else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
.padding(.vertical)
|
||||
}
|
||||
.listStyle(InsetGroupedListStyle())
|
||||
.navigationTitle(Text("Libraries"))
|
||||
}
|
||||
}
|
||||
|
||||
struct LicensesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LicensesView()
|
||||
}
|
||||
}
|
32
wulkanowy/Views/Content/messages.swift
Normal file
32
wulkanowy/Views/Content/messages.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// messages.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 26/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MessagesView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (messages)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct MessagesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MessagesView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
50
wulkanowy/Views/Content/more.swift
Normal file
50
wulkanowy/Views/Content/more.swift
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// more.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 24/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MoreView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section {
|
||||
NavigationLink(destination: MessagesView()) {
|
||||
Label("messagesButton", systemImage: "envelope")
|
||||
.accessibility(label: Text("messagesButton"))
|
||||
}
|
||||
NavigationLink(destination: NotesView()) {
|
||||
Label("notesButton", systemImage: "graduationcap")
|
||||
.accessibility(label: Text("notesButton"))
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink(destination: SettingsView()) {
|
||||
Label("settingsButton", systemImage: "gear")
|
||||
.accessibility(label: Text("settingsButton"))
|
||||
}
|
||||
NavigationLink(destination: AboutView()) {
|
||||
Label("aboutButton", systemImage: "info.circle")
|
||||
.accessibility(label: Text("aboutButton"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle("moreButton", displayMode: .inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct MoreView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MoreView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
32
wulkanowy/Views/Content/notes.swift
Normal file
32
wulkanowy/Views/Content/notes.swift
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// notes.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 26/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NotesView: View {
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
Text("You are not logged in (notes)")
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Text("Log in")
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct NotesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NotesView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
25
wulkanowy/Views/Content/settings.swift
Normal file
25
wulkanowy/Views/Content/settings.swift
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// settings.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 26/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
var body: some View {
|
||||
Text("Here are settings (in my imagination)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
SettingsView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
//
|
||||
<<<<<<< HEAD
|
||||
// LoginView.swift
|
||||
// wulkanowy
|
||||
//
|
||||
|
@ -41,11 +42,202 @@ struct LoginView: View {
|
|||
.sheet(isPresented: $needsAppOnboarding) {
|
||||
OnboardingView()
|
||||
}
|
||||
=======
|
||||
// ContentView.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Mikołaj on 25/10/2020.
|
||||
//
|
||||
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum AvailableEndpoints: String, CaseIterable {
|
||||
case vulcan = "Vulcan"
|
||||
case fakelog = "Fakelog"
|
||||
}
|
||||
|
||||
|
||||
struct LoginView: View {
|
||||
|
||||
@StateObject var vulcan: VulcanStore = VulcanStore.shared
|
||||
|
||||
@State private var token: String = ""
|
||||
@State private var symbol: String = ""
|
||||
@State private var pin: String = ""
|
||||
@State private var deviceModel: String = ""
|
||||
|
||||
@State private var clicked: Bool = false
|
||||
@State private var buttonValue = String(format: NSLocalizedString("loginButton", comment: "loginButton"))
|
||||
@State private var loginStatus: String = ""
|
||||
@State private var willMoveToNextScreen = false
|
||||
|
||||
let cellHeight: CGFloat = 55
|
||||
let cornerRadius: CGFloat = 12
|
||||
let cellBackground: Color = Color(UIColor.systemGray6).opacity(0.5)
|
||||
|
||||
let nullColor: Color = Color.accentColor.opacity(0.4)
|
||||
|
||||
private func login() {
|
||||
clicked = true
|
||||
if(token != "" && symbol != "" && pin != "" && deviceModel != "") {
|
||||
vulcan.login(token: token, symbol: symbol, pin: pin, deviceModel: deviceModel) { error in
|
||||
if let error = error {
|
||||
print("error: \(error)")
|
||||
switch("\(error)"){
|
||||
case "wrongToken":
|
||||
buttonValue = String(format: NSLocalizedString("\(error)", comment: "loginButton"))
|
||||
|
||||
case "wrongSymbol":
|
||||
buttonValue = String(format: NSLocalizedString("\(error)", comment: "loginButton"))
|
||||
|
||||
case "wrongPin":
|
||||
buttonValue = String(format: NSLocalizedString("\(error)", comment: "loginButton"))
|
||||
|
||||
default:
|
||||
buttonValue = String(format: NSLocalizedString("invalidData", comment: "loginButton"))
|
||||
}
|
||||
} else {
|
||||
print("success")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func setColor(input: String) -> Color {
|
||||
if(clicked == true){
|
||||
switch(input) {
|
||||
case "token":
|
||||
if (token == "") {
|
||||
return nullColor
|
||||
} else {
|
||||
return cellBackground
|
||||
}
|
||||
|
||||
case "symbol":
|
||||
if (symbol == "") {
|
||||
return nullColor
|
||||
} else {
|
||||
return cellBackground
|
||||
}
|
||||
|
||||
case "pin":
|
||||
if (pin == "") {
|
||||
return nullColor
|
||||
} else {
|
||||
return cellBackground
|
||||
}
|
||||
|
||||
case "deviceName":
|
||||
if (deviceModel == "") {
|
||||
return nullColor
|
||||
} else {
|
||||
return cellBackground
|
||||
}
|
||||
|
||||
default:
|
||||
return cellBackground
|
||||
}
|
||||
} else {
|
||||
return cellBackground
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image("wulkanowy")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 92)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("loginTitle")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
Spacer()
|
||||
|
||||
TextField("token", text: $token)
|
||||
.autocapitalization(.none)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(setColor(input: "token"), lineWidth: 2)
|
||||
)
|
||||
|
||||
TextField("symbol", text: $symbol)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(setColor(input: "symbol"), lineWidth: 2)
|
||||
)
|
||||
|
||||
TextField("pin", text: $pin)
|
||||
.keyboardType(.numberPad)
|
||||
.autocapitalization(.none)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(setColor(input: "pin"), lineWidth: 2)
|
||||
)
|
||||
|
||||
TextField("deviceName", text: $deviceModel)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(Font.body.weight(Font.Weight.medium))
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.background(cellBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(setColor(input: "deviceName"), lineWidth: 2)
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(buttonValue) {login()}
|
||||
.font(.headline)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
.frame(height: cellHeight)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.accentColor.opacity(0.1))
|
||||
.cornerRadius(cornerRadius)
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
>>>>>>> feature/register-device
|
||||
}
|
||||
}
|
||||
|
||||
struct LoginView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
<<<<<<< HEAD
|
||||
LoginView()
|
||||
=======
|
||||
Group {
|
||||
LoginView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
>>>>>>> feature/register-device
|
||||
}
|
||||
}
|
||||
|
|
55
wulkanowy/Views/Navigation/navigation.swift
Normal file
55
wulkanowy/Views/Navigation/navigation.swift
Normal file
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// navigation.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 23/02/2021.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct NavigationBarView: View {
|
||||
var body: some View {
|
||||
TabView() {
|
||||
DashboardView()
|
||||
.tabItem {
|
||||
Label("dashboardButton", systemImage: "rectangle.on.rectangle")
|
||||
.accessibility(label: Text("dashboardButton"))
|
||||
}
|
||||
|
||||
GradesView()
|
||||
.tabItem {
|
||||
Label("gradesButton", systemImage: "rosette")
|
||||
.accessibility(label: Text("gradesButton"))
|
||||
}
|
||||
|
||||
ExamsView()
|
||||
.tabItem {
|
||||
Label("examsButton", systemImage: "calendar")
|
||||
.accessibility(label: Text("examsButton"))
|
||||
}
|
||||
|
||||
HomeworksView()
|
||||
.tabItem {
|
||||
Label("homeworkButton", systemImage: "note.text")
|
||||
.accessibility(label: Text("homeworkButton"))
|
||||
}
|
||||
|
||||
MoreView()
|
||||
.tabItem {
|
||||
Label("moreButton", systemImage: "ellipsis.circle")
|
||||
.accessibility(label: Text("moreButton"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct NavigationBarView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
NavigationBarView()
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
|
123
wulkanowy/Views/ghImage.swift
Normal file
123
wulkanowy/Views/ghImage.swift
Normal file
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// ghImage.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz on 27/02/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
struct AsyncImage<Placeholder: View>: View {
|
||||
@StateObject private var loader: ImageLoader
|
||||
private let placeholder: Placeholder
|
||||
private let image: (UIImage) -> Image
|
||||
|
||||
init(
|
||||
url: URL,
|
||||
@ViewBuilder placeholder: () -> Placeholder,
|
||||
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
|
||||
) {
|
||||
self.placeholder = placeholder()
|
||||
self.image = image
|
||||
_loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
content
|
||||
.onAppear(perform: loader.load)
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
Group {
|
||||
if loader.image != nil {
|
||||
image(loader.image!)
|
||||
} else {
|
||||
placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol ImageCache {
|
||||
subscript(_ url: URL) -> UIImage? { get set }
|
||||
}
|
||||
|
||||
struct TemporaryImageCache: ImageCache {
|
||||
private let cache = NSCache<NSURL, UIImage>()
|
||||
|
||||
subscript(_ key: URL) -> UIImage? {
|
||||
get { cache.object(forKey: key as NSURL) }
|
||||
set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
|
||||
}
|
||||
}
|
||||
|
||||
class ImageLoader: ObservableObject {
|
||||
@Published var image: UIImage?
|
||||
|
||||
private(set) var isLoading = false
|
||||
|
||||
private let url: URL
|
||||
private var cache: ImageCache?
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
|
||||
|
||||
init(url: URL, cache: ImageCache? = nil) {
|
||||
self.url = url
|
||||
self.cache = cache
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancel()
|
||||
}
|
||||
|
||||
func load() {
|
||||
guard !isLoading else { return }
|
||||
|
||||
if let image = cache?[url] {
|
||||
self.image = image
|
||||
return
|
||||
}
|
||||
|
||||
cancellable = URLSession.shared.dataTaskPublisher(for: url)
|
||||
.map { UIImage(data: $0.data) }
|
||||
.replaceError(with: nil)
|
||||
.handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
|
||||
receiveOutput: { [weak self] in self?.cache($0) },
|
||||
receiveCompletion: { [weak self] _ in self?.onFinish() },
|
||||
receiveCancel: { [weak self] in self?.onFinish() })
|
||||
.subscribe(on: Self.imageProcessingQueue)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in self?.image = $0 }
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
cancellable?.cancel()
|
||||
}
|
||||
|
||||
private func onStart() {
|
||||
isLoading = true
|
||||
}
|
||||
|
||||
private func onFinish() {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
private func cache(_ image: UIImage?) {
|
||||
image.map { cache?[url] = $0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct ImageCacheKey: EnvironmentKey {
|
||||
static let defaultValue: ImageCache = TemporaryImageCache()
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var imageCache: ImageCache {
|
||||
get { self[ImageCacheKey.self] }
|
||||
set { self[ImageCacheKey.self] = newValue }
|
||||
}
|
||||
}
|
61
wulkanowy/VulcanStore.swift
Normal file
61
wulkanowy/VulcanStore.swift
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// VulcanStore.swift
|
||||
// wulkanowy
|
||||
//
|
||||
// Created by Tomasz (copied from rrroyal/vulcan) on 16/02/2021.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Sdk
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
final class VulcanStore: ObservableObject {
|
||||
static let shared: VulcanStore = VulcanStore()
|
||||
|
||||
var privateKey: String?
|
||||
let sdk: Sdk?
|
||||
private init() {
|
||||
// Check for stored certificate
|
||||
guard let certificate: X509 = try? X509(serialNumber: 1, certificateEntries: ["CN": "APP_CERTIFICATE CA Certificate"]) else {
|
||||
sdk = nil
|
||||
privateKey = nil
|
||||
return
|
||||
}
|
||||
|
||||
guard let privateKeyRawData = certificate.getPrivateKeyData(format: .DER),
|
||||
let privateKeyString = String(data: privateKeyRawData, encoding: .utf8)?
|
||||
.split(separator: "\n")
|
||||
.dropFirst()
|
||||
.dropLast()
|
||||
.joined() else {
|
||||
privateKey = nil
|
||||
sdk = nil
|
||||
return
|
||||
}
|
||||
|
||||
privateKey = privateKeyString
|
||||
|
||||
sdk = Sdk(certificate: certificate)
|
||||
}
|
||||
|
||||
public func login(token: String, symbol: String, pin: String, deviceModel: String, completionHandler: @escaping (Error?) -> Void) {
|
||||
sdk?.login(token: token, symbol: symbol, pin: pin, deviceModel: deviceModel) { [self] error in
|
||||
if let error = error {
|
||||
// Wyobraź sobie, że tutaj jest obsługa błędów. Wyobraź, bo mi sie jej robić nie chciało.
|
||||
print(error)
|
||||
} else {
|
||||
let privateKeyToSave: String = privateKey ?? ""
|
||||
|
||||
let utf8str = privateKeyToSave.data(using: .utf8)
|
||||
|
||||
if let base64Encoded = utf8str?.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)) {
|
||||
let keychain = Keychain()
|
||||
keychain[string: "privateKey"] = base64Encoded
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
import XCTest
|
||||
@testable import wulkanowy
|
||||
|
||||
@available (iOS 14, macOS 11, watchOS 7, tvOS 14, *)
|
||||
class wulkanowyTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
|
|
Loading…
Reference in a new issue