diff --git a/ios/Packages/PolkadotIdenticon/.gitignore b/ios/Packages/PolkadotIdenticon/.gitignore new file mode 100644 index 0000000000..3b29812086 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/ios/Packages/PolkadotIdenticon/Package.swift b/ios/Packages/PolkadotIdenticon/Package.swift new file mode 100644 index 0000000000..acaa52a5e4 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Package.swift @@ -0,0 +1,33 @@ +// swift-tools-version: 5.7 + +import PackageDescription + +let package = Package( + name: "PolkadotIdenticon", + platforms: [ + .iOS(.v15) + ], + products: [ + .library( + name: "PolkadotIdenticon", + targets: ["PolkadotIdenticon"] + ) + ], + dependencies: [ + .package(url: "https://github.com/tesseract-one/Blake2.swift.git", from: "0.1.0"), + .package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0") + ], + targets: [ + .target( + name: "PolkadotIdenticon", + dependencies: [ + .product(name: "Blake2", package: "Blake2.swift"), + .product(name: "BigInt", package: "BigInt") + ] + ), + .testTarget( + name: "PolkadotIdenticonTests", + dependencies: ["PolkadotIdenticon"] + ) + ] +) diff --git a/ios/Packages/PolkadotIdenticon/README.md b/ios/Packages/PolkadotIdenticon/README.md new file mode 100644 index 0000000000..9b651f7972 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/README.md @@ -0,0 +1,3 @@ +# Polkadot Identicon + +A description of this package. diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/CircleLayoutGenerator.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/CircleLayoutGenerator.swift new file mode 100644 index 0000000000..410d525e03 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/CircleLayoutGenerator.swift @@ -0,0 +1,81 @@ +// +// CircleLayoutGenerator.swift +// +// +// Created by Krzysztof Rodak on 24/07/2023. +// + +import Foundation + +public final class CircleLayoutGenerator { + public init() {} + /// Returns an array of circles with assigned positions, radii and colors. + /// The circles are positioned in a radial layout and each is assigned a color from the provided array. + /// - Parameters: + /// - distanceBetweenCenters: The distance between the centers of adjacent circles. + /// - circleRadius: The radius of each circle. + /// - colors: An array of colors to be assigned to the circles. The colors are assigned in the order they appear + /// in the array. + /// - Returns: An array of circles with assigned positions, radii and colors. + func generateCircles(distanceBetweenCenters: Float, circleRadius: Float, colors: [Color]) -> [Circle] { + calculateCirclePositions(distanceBetweenCenters: distanceBetweenCenters) + .enumerated() + .map { index, position in Circle( + position: .init(centerX: position.centerX, centerY: position.centerY), + radius: circleRadius, + rgba_color: colors[index] + ) } + } +} + +private extension CircleLayoutGenerator { + /// Calculates the positions for the centers of the circles in a radial layout. + /// + /// The layout is as follows: + /// + /// 0 + /// + /// 2 17 + /// + /// 3 1 15 + /// + /// 4 16 + /// + /// 5 18 14 + /// + /// 7 13 + /// + /// 6 10 12 + /// + /// 8 11 + /// + /// 9 + /// + /// - Parameter distanceBetweenCenters: The distance between the centers of adjacent circles. + /// - Returns: An array of positions for the circle centers. + func calculateCirclePositions(distanceBetweenCenters: Float) -> [CirclePosition] { + let a = distanceBetweenCenters + let b = distanceBetweenCenters * sqrt(3) / 2 + return [ + CirclePosition(centerX: 0, centerY: -2 * a), + CirclePosition(centerX: 0, centerY: -a), + CirclePosition(centerX: -b, centerY: -3 * a / 2), + CirclePosition(centerX: -2 * b, centerY: -a), + CirclePosition(centerX: -b, centerY: -a / 2), + CirclePosition(centerX: -2 * b, centerY: 0), + CirclePosition(centerX: -2 * b, centerY: a), + CirclePosition(centerX: -b, centerY: a / 2), + CirclePosition(centerX: -b, centerY: 3 * a / 2), + CirclePosition(centerX: 0, centerY: 2 * a), + CirclePosition(centerX: 0, centerY: a), + CirclePosition(centerX: b, centerY: 3 * a / 2), + CirclePosition(centerX: 2 * b, centerY: a), + CirclePosition(centerX: b, centerY: a / 2), + CirclePosition(centerX: 2 * b, centerY: 0), + CirclePosition(centerX: 2 * b, centerY: -a), + CirclePosition(centerX: b, centerY: -a / 2), + CirclePosition(centerX: b, centerY: -3 * a / 2), + CirclePosition(centerX: 0, centerY: 0) + ] + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonColorsGenerator.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonColorsGenerator.swift new file mode 100644 index 0000000000..5616bbf34f --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonColorsGenerator.swift @@ -0,0 +1,194 @@ +// +// IdenticonColorsGenerator.swift +// +// +// Created by Krzysztof Rodak on 31/07/2023. +// + +import Blake2 +import Foundation + +public final class IdenticonColorsGenerator { + private enum Constants { + static let byteHashLength = 64 + static let arrayZeroBytesLength = 32 + static let derivedIDRotationFactorMultiplier: UInt8 = 6 + static let derivedIDRotationFactorModulo: UInt8 = 3 + static let hueDegrees = 360 + static let colorArrayLength = 19 + static let lightnessPercentages = [53, 15, 35, 75] + } + + public init() {} + + /// Returns an array of 19 colors based on the input data. + /// + /// The function first hashes the input data and a zero-filled array with the Blake2b hashing algorithm. + /// It then derives a unique ID by subtracting the hashed zero-filled array from the hashed input data. + /// The ID is used to calculate a saturation component and derive a palette of 19 colors. + /// Finally, it selects a color scheme based on the derived ID and rotates the colors in the scheme. + /// + /// - Parameter inputData: A byte array to derive the colors from. + /// - Returns: An array of 19 derived colors. + func deriveColors(from inputData: [UInt8]) -> [Color] { + let zeroBytes = Array(repeating: UInt8(0), count: Constants.arrayZeroBytesLength) + + guard let hashedInput = try? Blake2.hash(.b2b, size: Constants.byteHashLength, bytes: inputData), + let hashedZeroBytes = try? Blake2.hash(.b2b, size: Constants.byteHashLength, bytes: zeroBytes) else { + return [] + } + + // Create an ID array by subtracting elements of hashedInput and zeroBytes. + var derivedID: [UInt8] = [] + for (index, byte) in hashedInput.enumerated() { + let newValue = byte &- hashedZeroBytes[index] + derivedID.append(newValue) + } + + let colorPalette = deriveColors(derivedID: derivedID) + + // Choose the color scheme based on the 30th and 31st byte of the derived ID. + let colorSchemes = ColorScheme.defaultColorSchemes + let totalFrequency: Int = colorSchemes.reduce(0) { $0 + $1.frequency } + let selectionFactor = (UInt(derivedID[30]) + UInt(derivedID[31]) * 256) % UInt(totalFrequency) + let selectedScheme = chooseScheme(schemes: colorSchemes, selectionFactor: selectionFactor) + + // Calculate rotation factor for the color scheme. + let rotationFactor = (derivedID[28] % Constants.derivedIDRotationFactorMultiplier) * Constants + .derivedIDRotationFactorModulo + + // Generate the final array of colors using selected color scheme and rotation factor. + return (0 ..< Constants.colorArrayLength).map { index in + let colorIndex = index < Constants + .colorArrayLength - 1 ? (index + Int(rotationFactor)) % (Constants.colorArrayLength - 1) : Constants + .colorArrayLength - 1 + let paletteIndex = selectedScheme.colorPaletteIndices[colorIndex] + return colorPalette[paletteIndex] + } + } + + private func deriveColors(derivedID: [UInt8]) -> [Color] { + // Calculate saturation component using 29th byte of the derived ID. + let sat = UInt8((((UInt(derivedID[29]) * 70) / 256 + 26) % 80) + 30) + let saturationComponent: Double = Double(sat) / 100.0 + + // Generate palette of colors using derived ID and saturation component. + return derivedID + .enumerated() + .map { index, byte in + let byteColor = byte &+ (UInt8(index % 28) &* 58) + switch byteColor { + case 0: + return Color(red: 4, green: 4, blue: 4, alpha: 255) + case 255: + return Color.foregroundColor + default: + return derive(fromByte: byteColor, saturationComponent: saturationComponent) + } + } + } + + /// Derives a color from a byte and a saturation component. + /// + /// - Parameters: + /// - byte: The byte to derive the color from. + /// - saturationComponent: The saturation component to use. + /// - Returns: The derived color. + private func derive(fromByte byte: UInt8, saturationComponent: Double) -> Color { + // HSL color hue in degrees + let hueModulus = 64 + let hue = Int(byte % UInt8(hueModulus)) * Constants.hueDegrees / hueModulus + // HSL lightness in percents + let lightnessIndexFactor: UInt8 = 64 + let lightnessIndex = byte / lightnessIndexFactor + let lightnessPercentage = lightnessIndex < Constants.lightnessPercentages.count ? Constants + .lightnessPercentages[Int(lightnessIndex)] : 0 + let lightnessComponent: Double = Double(lightnessPercentage) / 100.0 + let (red, green, blue) = hslToRgb( + hue: Double(hue), + saturation: saturationComponent, + lightness: lightnessComponent + ) + + return Color(red: red, green: green, blue: blue, alpha: 255) + } + + /// Choose a color scheme based on a selection factor. + /// + /// - Parameters: + /// - schemes: An array of color schemes. + /// - selectionFactor: A selection factor to determine which color scheme to use. + /// - Returns: The selected color scheme. + private func chooseScheme(schemes: [ColorScheme], selectionFactor: UInt) -> ColorScheme { + var sum: UInt = 0 + var foundScheme: ColorScheme? + for scheme in schemes { + sum += UInt(scheme.frequency) + if selectionFactor < sum { + foundScheme = scheme + break + } + } + return foundScheme! + } + + /// Converts HSL color space values to RGB color space values. + /// + /// - Parameters: + /// - hue: The hue value of the HSL color, specified as a degree between 0 and 360. + /// - saturation: The saturation value of the HSL color, specified as a double between 0 and 1. + /// - lightness: The lightness value of the HSL color, specified as a double between 0 and 1. + /// - Returns: A tuple representing the RGB color values, each a UInt8 between 0 and 255. + private func hslToRgb( + hue: Double, + saturation: Double, + lightness: Double + ) -> (red: UInt8, green: UInt8, blue: UInt8) { + var redComponent: Double = 0.0 + var greenComponent: Double = 0.0 + var blueComponent: Double = 0.0 + + let normalizedHue = hue / 360.0 + + if saturation == 0.0 { + // Achromatic color (gray scale) + redComponent = lightness + greenComponent = lightness + blueComponent = lightness + } else { + let qValue = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * + saturation + let pValue = 2 * lightness - qValue + + redComponent = convertHueToRgbComponent(p: pValue, q: qValue, hueShift: normalizedHue + 1 / 3) + greenComponent = convertHueToRgbComponent(p: pValue, q: qValue, hueShift: normalizedHue) + blueComponent = convertHueToRgbComponent(p: pValue, q: qValue, hueShift: normalizedHue - 1 / 3) + } + + return ( + red: UInt8(max(min(floor(redComponent * 256), 255), 0)), + green: UInt8(max(min(floor(greenComponent * 256), 255), 0)), + blue: UInt8(max(min(floor(blueComponent * 256), 255), 0)) + ) + } + + /// Calculates a single RGB color component from HSL values. + /// + /// - Parameters: + /// - p: The first helper value derived from the lightness value of the HSL color. + /// - q: The second helper value derived from the lightness and saturation values of the HSL color. + /// - hueShift: The hue value of the HSL color, shifted by a certain amount. + /// - Returns: A double representing the calculated RGB color component. + private func convertHueToRgbComponent(p: Double, q: Double, hueShift: Double) -> Double { + var shiftedHue = hueShift + + if shiftedHue < 0 { shiftedHue += 1 } + if shiftedHue > 1 { shiftedHue -= 1 } + + if shiftedHue < 1 / 6 { return p + (q - p) * 6 * shiftedHue } + if shiftedHue < 1 / 2 { return q } + if shiftedHue < 2 / 3 { return p + (q - p) * (2 / 3 - shiftedHue) * 6 } + + return p + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonImageRenderer.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonImageRenderer.swift new file mode 100644 index 0000000000..235b11a3f1 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/IdenticonImageRenderer.swift @@ -0,0 +1,114 @@ +// +// IdenticonImageRenderer.swift +// +// +// Created by Krzysztof Rodak on 01/08/2023. +// + +import CoreGraphics +import UIKit + +/// `IdenticonImageRenderer` is responsible for generating an image for a given set of colors. +/// It utilizes `CircleLayoutGenerator` to generate a layout of circles and colors them accordingly. +public final class IdenticonImageRenderer { + private enum Constants { + static let fullImageRadiusFactor: Float = 0.5 + static let individualCircleRadiusFactor: Float = 0.15625 // 5 / 32 + static let distanceBetweenCentersFactor: Float = 0.375 // 3 / 8 + } + + private let circleLayoutGenerator: CircleLayoutGenerator + + /// Initializes a new instance of `IdenticonImageRenderer` with a given `CircleLayoutGenerator`. + /// + /// - Parameter circleLayoutGenerator: A `CircleLayoutGenerator` used to generate the layout of circles. + public init(circleLayoutGenerator: CircleLayoutGenerator = CircleLayoutGenerator()) { + self.circleLayoutGenerator = circleLayoutGenerator + } + + /// Generates an image of the specified size and colors the circles according to the supplied colors array. + /// + /// - Parameters: + /// - size: The size (width and height) of the image. + /// - colors: An array of colors to be used to color the circles. + /// - Returns: A `UIImage` representing the generated Identicon image. + func generateImage(size: CGFloat, colors: [Color]) -> UIImage { + let fullImageRadius = Float(size) * Constants.fullImageRadiusFactor + let individualCircleRadius = fullImageRadius * Constants.individualCircleRadiusFactor + let distanceBetweenCenters = fullImageRadius * Constants.distanceBetweenCentersFactor + let largeBackgroundCircle = Circle( + position: .init(centerX: 0, centerY: 0), + radius: fullImageRadius, + rgba_color: Color.foregroundColor + ) + let smallerCirclesSet = circleLayoutGenerator.generateCircles( + distanceBetweenCenters: distanceBetweenCenters, + circleRadius: individualCircleRadius, + colors: colors + ) + + return createImage( + size: size, + largeBackgroundCircle: largeBackgroundCircle, + smallerCirclesSet: smallerCirclesSet, + fullImageRadius: fullImageRadius + ) + } +} + +private extension IdenticonImageRenderer { + func createImage( + size: CGFloat, + largeBackgroundCircle: Circle, + smallerCirclesSet: [Circle], + fullImageRadius: Float + ) -> UIImage { + UIGraphicsImageRenderer(size: CGSize(width: size, height: size)).image { ctx in + drawLargeBackgroundCircle(ctx: ctx, largeBackgroundCircle: largeBackgroundCircle, size: size) + smallerCirclesSet.forEach { + drawCircle(ctx: ctx, circle: $0, fullImageRadius: fullImageRadius, size: size) + } + } + } + + func drawLargeBackgroundCircle( + ctx: UIGraphicsImageRendererContext, + largeBackgroundCircle: Circle, + size: CGFloat + ) { + let fullImageRect = CGRect(x: 0, y: 0, width: size, height: size) + ctx.cgContext.setFillColor(largeBackgroundCircle.rgba_color.toCGColor()) + ctx.cgContext.fillEllipse(in: fullImageRect) + } + + func drawCircle( + ctx: UIGraphicsImageRendererContext, + circle: Circle, + fullImageRadius: Float, + size: CGFloat + ) { + let circleRectangle = calculateCircleRectangle(circle: circle, fullImageRadius: fullImageRadius, size: size) + ctx.cgContext.setFillColor(circle.rgba_color.toCGColor()) + ctx.cgContext.fillEllipse(in: circleRectangle) + } + + /// Calculate the rectangle that will enclose a circle in the final image. + /// + /// - Parameters: + /// - circle: The circle for which to calculate the rectangle. + /// - fullImageRadius: The radius of the full image. + /// - size: The size of the image. + /// - Returns: A `CGRect` that will enclose the circle. + func calculateCircleRectangle( + circle: Circle, + fullImageRadius: Float, + size: CGFloat + ) -> CGRect { + let x = circle.position.centerX * Float(size) / (2 * fullImageRadius) + Float(size) / 2 + let y = circle.position.centerY * Float(size) / (2 * fullImageRadius) + Float(size) / 2 + let circleRectX = CGFloat(x - circle.radius) + let circleRectY = CGFloat(y - circle.radius) + let circleSize = CGFloat(2 * circle.radius) + return CGRect(x: circleRectX, y: circleRectY, width: circleSize, height: circleSize) + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/PublicKeyDecoder.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/PublicKeyDecoder.swift new file mode 100644 index 0000000000..ecaa41a927 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Helpers/PublicKeyDecoder.swift @@ -0,0 +1,101 @@ +// +// PublicKeyDecoder.swift +// +// +// Created by Krzysztof Rodak on 23/07/2023. +// + +import BigInt +import Foundation + +/// Represents a public key in various possible formats. +/// +/// This enum can hold a public key in three formats: raw `Data`, a `hex` encoded string, or a `base58` encoded string. +public enum PublicKey { + /// Represents a public key as raw `Data`. + /// + /// The associated `Data` value is a byte buffer containing the raw bytes of the public key. + case data(Data) + + /// Represents a public key as a `hex` encoded string. + /// + /// The associated `String` value is a hexadecimal string representation of the public key. Each byte of the key is + /// represented as a two-digit hexadecimal number, from `00` to `ff`. + case hex(String) + + /// Represents a public key as a `base58` encoded string. + /// + /// The associated `String` value is a Base58 string representation of the public key. Base58 is a binary-to-text + /// encoding scheme that is designed to be human-readable and that minimizes the risk of mistyping. + case base58(String) +} + +/// `PublicKeyDecoder` converts public keys from various formats to byte arrays. +public final class PublicKeyDecoder { + private enum Constants { + static let base58Alphabet = [UInt8]("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".utf8) + static let zero = BigUInt(0) + static let radix = BigUInt(Constants.base58Alphabet.count) + } + + public init() {} + + /// Convert a public key to a byte array. + /// - Parameter publicKey: The public key to convert. + /// - Returns: The public key as a byte array. + func keyAsData(_ publicKey: PublicKey) -> [UInt8] { + switch publicKey { + case let .data(data): + return [UInt8](data) + case let .hex(string): + return hexToBytes(hex: string) + case let .base58(string): + return base58ToBytes(base58: string) + } + } +} + +private extension PublicKeyDecoder { + /// Convert a hexadecimal string to a byte array. + /// - Parameter hex: The hexadecimal string to convert. + /// - Returns: The byte array. + func hexToBytes(hex: String) -> [UInt8] { + var startIndex = hex.startIndex + var bytes = [UInt8]() + while startIndex < hex.endIndex { + let endIndex = hex.index(startIndex, offsetBy: 2) + let substr = hex[startIndex ..< endIndex] + if let byte = UInt8(substr, radix: 16) { + bytes.append(byte) + } + startIndex = endIndex + } + return bytes + } + + /// Convert a base58 string to a byte array. + /// - Parameter base58: The base58 string to convert. + /// - Returns: The byte array. + func base58ToBytes(base58: String) -> [UInt8] { + var currentRadixPower = BigUInt(1) + let base58Bytes: [UInt8] = Array(base58.utf8) + + // Convert base58 characters to their corresponding numeric values + let base58NumericValue = base58Bytes.reversed() + .enumerated() + .reduce(into: Constants.zero) { result, indexedCharacter in + guard let base58CharacterIndex = Constants.base58Alphabet.firstIndex(of: indexedCharacter.element) + else { return } + result += (currentRadixPower * BigUInt(base58CharacterIndex)) + currentRadixPower *= Constants.radix + } + + let decodedBytes = base58NumericValue.serialize() + + var result = Array(base58Bytes.prefix { $0 == Constants.base58Alphabet[0] }) + decodedBytes + // Drop 42 substrate prefix and last 2 elements + result = Array(result.dropFirst().dropLast(2)) + + return result + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Circles.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Circles.swift new file mode 100644 index 0000000000..ad2d15f40c --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Circles.swift @@ -0,0 +1,36 @@ +// +// Circles.swift +// +// +// Created by Krzysztof Rodak on 24/07/2023. +// + +import Foundation +import UIKit + +/// `Circle` represents a circle used in generated Identicon. +/// +/// It consists of a `CirclePosition` representing its center, a radius, and an RGBA color. +/// `Circle` instances are used in the process of creating visual identifiers or Identicons. +struct Circle: Equatable { + /// The position of the circle's center. + let position: CirclePosition + + /// The radius of the circle. + let radius: Float + + /// The color of the circle in RGBA format. + let rgba_color: Color +} + +/// `CirclePosition` represents the position of a circle's center in a two-dimensional space. +/// +/// It is defined by two floating-point numbers representing the x (horizontal) and y (vertical) coordinates of the +/// circle's center. +struct CirclePosition: Equatable { + /// The x-coordinate of the circle's center. + let centerX: Float + + /// The y-coordinate of the circle's center. + let centerY: Float +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Color.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Color.swift new file mode 100644 index 0000000000..8e842c70f8 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/Color.swift @@ -0,0 +1,51 @@ +// +// Color.swift +// +// +// Created by Krzysztof Rodak on 24/07/2023. +// + +import UIKit + +/// `Color` represents a color using RGBA (Red, Green, Blue, Alpha) components. +/// +/// Each component is represented by a `UInt8` (0-255). The `Color` structure provides +/// pre-defined static colors for foreground and background, and supports the conversion +/// to `CGColor` for use in drawing operations. +struct Color: Equatable { + /// The red component of the color. + let red: UInt8 + + /// The green component of the color. + let green: UInt8 + + /// The blue component of the color. + let blue: UInt8 + + /// The alpha component of the color. + let alpha: UInt8 + + /// A predefined color white with an alpha of 0, typically used as the background color. + /// + /// - Returns: A `Color` instance representing the background color. + static let backgroundColor: Color = Color(red: 255, green: 255, blue: 255, alpha: 0) + + /// A predefined gray color with an alpha of 255, typically used as the foreground color. + /// + /// - Returns: A `Color` instance representing the foreground color. + static let foregroundColor: Color = Color(red: 238, green: 238, blue: 238, alpha: 255) +} + +extension Color { + /// Converts the `Color` instance into a `CGColor` which can be used in UIKit's drawing APIs. + /// + /// - Returns: A `CGColor` representation of the color. + func toCGColor() -> CGColor { + UIColor( + red: CGFloat(red) / 255.0, + green: CGFloat(green) / 255.0, + blue: CGFloat(blue) / 255.0, + alpha: CGFloat(alpha) / 255.0 + ).cgColor + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/ColorScheme.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/ColorScheme.swift new file mode 100644 index 0000000000..0ccba4b5be --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Models/ColorScheme.swift @@ -0,0 +1,45 @@ +// +// ColorScheme.swift +// +// +// Created by Krzysztof Rodak on 24/07/2023. +// + +import Foundation + +/// `ColorScheme` represents a color configuration for the identicon generation. +/// +/// Each `ColorScheme` consists of a frequency and a list of colorPaletteIndices, +/// which correspond to predefined colors used in the identicon generation. +/// A collection of default color schemes is also provided. +struct ColorScheme: Equatable { + /// The frequency with which this color scheme is used in identicon generation. + let frequency: Int + + /// An array of indices referring to specific colors in a predefined color palette. + let colorPaletteIndices: [Int] + + /// A collection of predefined color schemes used in identicon generation. + /// + /// Each `ColorScheme` in this array represents a unique color configuration with a specific frequency and color + /// index array. + static let defaultColorSchemes: [ColorScheme] = + [ + ColorScheme( + frequency: 1, + colorPaletteIndices: [0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 1] + ), + ColorScheme(frequency: 20, colorPaletteIndices: [0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 5]), + ColorScheme(frequency: 16, colorPaletteIndices: [1, 2, 3, 1, 2, 4, 5, 5, 4, 1, 2, 3, 1, 2, 4, 5, 5, 4, 0]), + ColorScheme(frequency: 32, colorPaletteIndices: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 3]), + ColorScheme(frequency: 32, colorPaletteIndices: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6]), + ColorScheme( + frequency: 128, + colorPaletteIndices: [0, 1, 2, 3, 4, 5, 3, 4, 2, 0, 1, 6, 7, 8, 9, 7, 8, 6, 10] + ), + ColorScheme( + frequency: 128, + colorPaletteIndices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 6, 7, 5, 3, 4, 2, 11] + ) + ] +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonGenerator.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonGenerator.swift new file mode 100644 index 0000000000..33d330ed10 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonGenerator.swift @@ -0,0 +1,55 @@ +// +// PolkadotIdenticonGenerator.swift +// +// +// Created by Krzysztof Rodak on 02/08/2023. +// + +import UIKit + +/// `PolkadotIdenticonGenerator` is a class that generates Polkadot Identicons. +/// An Identicon is a unique visual identifier, typically a colored geometric pattern, +/// that corresponds to a unique data input such as a public key. This class provides +/// a method to generate an Identicon based on a public key, which is represented in +/// various formats: as binary data, a hexadecimal string, or a base58 string. +public final class PolkadotIdenticonGenerator { + private let colorsGenerator: IdenticonColorsGenerator + private let imageRenderer: IdenticonImageRenderer + private let publicKeyDecoder: PublicKeyDecoder + + /// Initializes a new instance of `PolkadotIdenticonGenerator`. + /// + /// - Parameters: + /// - colorsGenerator: An instance of `IdenticonColorsGenerator` used to generate colors + /// for the Identicon based on the input data. Defaults to a new instance of `IdenticonColorsGenerator`. + /// - imageRenderer: An instance of `IdenticonImageRenderer` used to render the final Identicon image + /// from the generated colors. Defaults to a new instance of `IdenticonImageRenderer`. + /// - publicKeyDecoder: An instance of `PublicKeyDecoder` used to decode the public key from its various possible + /// formats. + /// Defaults to a new instance of `PublicKeyDecoder`. + public init( + colorsGenerator: IdenticonColorsGenerator = IdenticonColorsGenerator(), + imageRenderer: IdenticonImageRenderer = IdenticonImageRenderer(), + publicKeyDecoder: PublicKeyDecoder = PublicKeyDecoder() + ) { + self.colorsGenerator = colorsGenerator + self.imageRenderer = imageRenderer + self.publicKeyDecoder = publicKeyDecoder + } + + /// Generates a Polkadot Identicon from the public key. + /// + /// The identicon is represented as an image, where the image's colors and patterns are uniquely + /// determined by the public key. The size of the image can be specified. + /// + /// - Parameters: + /// - publicKey: The public key based on which the Identicon is generated. It can be in one of three formats: + /// raw binary data (`Data`), a hexadecimal string, or a base58 string. + /// - size: The size of the Identicon image to be generated. + /// - Returns: A `UIImage` instance representing the generated Identicon, or nil if the image couldn't be generated. + public func generateIdenticonImage(from publicKey: PublicKey, size: CGFloat) -> UIImage? { + let inputAsData = publicKeyDecoder.keyAsData(publicKey) + let colors = colorsGenerator.deriveColors(from: inputAsData) + return imageRenderer.generateImage(size: size, colors: colors) + } +} diff --git a/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonView.swift b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonView.swift new file mode 100644 index 0000000000..0c132a2d4c --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Sources/PolkadotIdenticon/Public/PolkadotIdenticonView.swift @@ -0,0 +1,59 @@ +// +// PolkadotIdenticonView.swift +// +// +// Created by Krzysztof Rodak on 02/08/2023. +// + +import SwiftUI + +/// `PolkadotIdenticonView` is a SwiftUI view that renders a Polkadot Identicon. +/// +/// An Identicon is a visual representation of a public key value, typically used to represent user identities in the +/// context of cryptographic applications. +/// This struct uses `PolkadotIdenticonGenerator` to generate a unique image, based on a provided public key input, +/// which can then be rendered within the SwiftUI view hierarchy. +public struct PolkadotIdenticonView: View { + private let identiconGenerator: PolkadotIdenticonGenerator = PolkadotIdenticonGenerator() + + /// The public key input based on which the Identicon is generated. + /// The public key can be in one of three formats: raw binary data (`Data`), a hexadecimal string, or a base58 + /// string. + let publicKey: PublicKey + + /// The size of the Identicon image to be generated. + let size: CGFloat + + /// Initializes a new instance of `PolkadotIdenticonView`. + /// + /// - Parameters: + /// - publicKey: The public key input based on which the Identicon is generated. This could be any unique public + /// key. + /// - size: The size of the Identicon image to be generated. + public init(publicKey: PublicKey, size: CGFloat) { + self.publicKey = publicKey + self.size = size + } + + /// Defines the content and behavior of this view. + /// + /// The body property returns an `Image` view if an Identicon image can be generated from the public key, + /// or an `EmptyView` if not. + public var body: some View { + if let identiconImage = createIdenticonImage() { + Image(uiImage: identiconImage) + .resizable() + .frame(width: size, height: size) + .aspectRatio(contentMode: .fit) + } else { + EmptyView() + } + } + + /// Helper function to generate an Identicon image. + /// + /// - Returns: A `UIImage` instance representing the generated Identicon, or nil if the image couldn't be generated. + private func createIdenticonImage() -> UIImage? { + identiconGenerator.generateIdenticonImage(from: publicKey, size: size) + } +} diff --git a/ios/Packages/PolkadotIdenticon/Tests/PolkadotIdenticonTests/Helpers/PublicKeyDecoderTests.swift b/ios/Packages/PolkadotIdenticon/Tests/PolkadotIdenticonTests/Helpers/PublicKeyDecoderTests.swift new file mode 100644 index 0000000000..313c6d0919 --- /dev/null +++ b/ios/Packages/PolkadotIdenticon/Tests/PolkadotIdenticonTests/Helpers/PublicKeyDecoderTests.swift @@ -0,0 +1,67 @@ +// +// PublicKeyDecoderTests.swift +// +// +// Created by Krzysztof Rodak on 23/07/2023. +// + +import Foundation + +@testable import PolkadotIdenticon +import XCTest + +final class PublicKeyDecoderTests: XCTestCase { + private var publicKeyDecoder: PublicKeyDecoder! + + private let expectedOutput: [UInt8] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, + 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125 + ] + + override func setUp() { + super.setUp() + publicKeyDecoder = PublicKeyDecoder() + } + + override func tearDown() { + publicKeyDecoder = nil + super.tearDown() + } + + func testDecodeData() { + // Given + let dataPublicKey: PublicKey = .data(Data(expectedOutput)) + + // When + let result = publicKeyDecoder.keyAsData(dataPublicKey) + + // Then + XCTAssertEqual(result, expectedOutput) + } + + func testDecodeHex() { + // Given + let hexPublicKey: PublicKey = .hex("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") + + // When + let result = publicKeyDecoder.keyAsData(hexPublicKey) + + // Then + XCTAssertEqual(result, expectedOutput) + } + + func testDecodeBase58() { + // Given + let expectedOutput: [UInt8] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, + 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125 + ] + let base58PublicKey: PublicKey = .base58("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY") + + // When + let result = publicKeyDecoder.keyAsData(base58PublicKey) + + // Then + XCTAssertEqual(result, expectedOutput) + } +} diff --git a/ios/PolkadotVault.xcodeproj/project.pbxproj b/ios/PolkadotVault.xcodeproj/project.pbxproj index 46809fc27f..c6bec95874 100644 --- a/ios/PolkadotVault.xcodeproj/project.pbxproj +++ b/ios/PolkadotVault.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 6D0677AC29BB0C6000D76D90 /* AppLaunchMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0677AB29BB0C6000D76D90 /* AppLaunchMediator.swift */; }; 6D0677AE29BB0D0000D76D90 /* WarningStateMediator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0677AD29BB0D0000D76D90 /* WarningStateMediator.swift */; }; 6D07D370292B40D2001A0B79 /* TransactionSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D07D36F292B40D2001A0B79 /* TransactionSummaryView.swift */; }; + 6D09912A2A7AA10800DF75EC /* BlockiesIdenticonViewPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0991292A7AA10800DF75EC /* BlockiesIdenticonViewPreviews.swift */; }; 6D0BF95229F2BBF500F5B569 /* NetworkIconCapsuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0BF95129F2BBF500F5B569 /* NetworkIconCapsuleView.swift */; }; 6D0E188B291B82D000B59875 /* ProgressSnackbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0E188A291B82D000B59875 /* ProgressSnackbar.swift */; }; 6D0FA73B2907010E00E45BA6 /* ExportMultipleKeysModal+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0FA73A2907010E00E45BA6 /* ExportMultipleKeysModal+ViewModel.swift */; }; @@ -147,8 +148,9 @@ 6D71290E294C2A380048558C /* VerticalActionsBottomModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D71290D294C2A380048558C /* VerticalActionsBottomModal.swift */; }; 6D7129222952B7800048558C /* NetworkSettingsDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7129212952B7800048558C /* NetworkSettingsDetails.swift */; }; 6D7129252952C3D50048558C /* VerticalRoundedBackgroundContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7129242952C3D50048558C /* VerticalRoundedBackgroundContainer.swift */; }; - 6D749C5A2A6871530064D7E5 /* BlockiesIdenticonViewPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D749C592A6871530064D7E5 /* BlockiesIdenticonViewPreviews.swift */; }; 6D749C5C2A6871BA0064D7E5 /* Blockies in Frameworks */ = {isa = PBXBuildFile; productRef = 6D749C5B2A6871BA0064D7E5 /* Blockies */; }; + 6D749C602A69C6020064D7E5 /* PolkadotIdenticonViewPreviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D749C5F2A69C6020064D7E5 /* PolkadotIdenticonViewPreviews.swift */; }; + 6D749C622A69CE2C0064D7E5 /* PolkadotIdenticon in Frameworks */ = {isa = PBXBuildFile; productRef = 6D749C612A69CE2C0064D7E5 /* PolkadotIdenticon */; }; 6D77F31F296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F31E296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift */; }; 6D77F321296D0D4600044C7C /* GetAllNetworksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F320296D0D4600044C7C /* GetAllNetworksService.swift */; }; 6D77F323296D4D8900044C7C /* CreateDerivedKeyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F322296D4D8900044C7C /* CreateDerivedKeyService.swift */; }; @@ -401,6 +403,7 @@ 6D0677AB29BB0C6000D76D90 /* AppLaunchMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLaunchMediator.swift; sourceTree = ""; }; 6D0677AD29BB0D0000D76D90 /* WarningStateMediator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WarningStateMediator.swift; sourceTree = ""; }; 6D07D36F292B40D2001A0B79 /* TransactionSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSummaryView.swift; sourceTree = ""; }; + 6D0991292A7AA10800DF75EC /* BlockiesIdenticonViewPreviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockiesIdenticonViewPreviews.swift; sourceTree = ""; }; 6D0AE5572A6129100042282A /* Blockies */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Blockies; sourceTree = ""; }; 6D0BF95129F2BBF500F5B569 /* NetworkIconCapsuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkIconCapsuleView.swift; sourceTree = ""; }; 6D0E188A291B82D000B59875 /* ProgressSnackbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressSnackbar.swift; sourceTree = ""; }; @@ -497,7 +500,8 @@ 6D71290D294C2A380048558C /* VerticalActionsBottomModal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerticalActionsBottomModal.swift; sourceTree = ""; }; 6D7129212952B7800048558C /* NetworkSettingsDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingsDetails.swift; sourceTree = ""; }; 6D7129242952C3D50048558C /* VerticalRoundedBackgroundContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalRoundedBackgroundContainer.swift; sourceTree = ""; }; - 6D749C592A6871530064D7E5 /* BlockiesIdenticonViewPreviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockiesIdenticonViewPreviews.swift; sourceTree = ""; }; + 6D749C5D2A699F5D0064D7E5 /* PolkadotIdenticon */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = PolkadotIdenticon; sourceTree = ""; }; + 6D749C5F2A69C6020064D7E5 /* PolkadotIdenticonViewPreviews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotIdenticonViewPreviews.swift; sourceTree = ""; }; 6D77F31E296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateKeyNetworkSelectionView.swift; sourceTree = ""; }; 6D77F320296D0D4600044C7C /* GetAllNetworksService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAllNetworksService.swift; sourceTree = ""; }; 6D77F322296D4D8900044C7C /* CreateDerivedKeyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDerivedKeyService.swift; sourceTree = ""; }; @@ -680,6 +684,7 @@ 6D971ABC2941B1C600121A36 /* SVGView in Frameworks */, 6DEEA87E28AFBF5D00371ECA /* libsigner.a in Frameworks */, 6D971ACA2942E79100121A36 /* QRCode in Frameworks */, + 6D749C622A69CE2C0064D7E5 /* PolkadotIdenticon in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -754,6 +759,7 @@ 2D7A7BC826BA97AE0053C1E0 /* Components */ = { isa = PBXGroup; children = ( + 6D749C5E2A69C5F00064D7E5 /* Identicons */, 6DEB18ED2A0BAFA60013995E /* Progress */, 6D10EACC297114550063FB71 /* DerivationPathComponents.swift */, 6DEB18EC2A0BAEE10013995E /* TextFields */, @@ -773,7 +779,6 @@ 6DDEF13D28AE7C4B004CA2FD /* Buttons */, 6DDEF11928AE2DD3004CA2FD /* TabBar */, 6D2779C728B3D33100570055 /* NavigationBarView.swift */, - 6D749C592A6871530064D7E5 /* BlockiesIdenticonViewPreviews.swift */, ); path = Components; sourceTree = ""; @@ -937,6 +942,7 @@ 6D0AE5532A6128F00042282A /* Packages */ = { isa = PBXGroup; children = ( + 6D749C5D2A699F5D0064D7E5 /* PolkadotIdenticon */, 6D0AE5572A6129100042282A /* Blockies */, ); path = Packages; @@ -1344,6 +1350,15 @@ path = Backgrounds; sourceTree = ""; }; + 6D749C5E2A69C5F00064D7E5 /* Identicons */ = { + isa = PBXGroup; + children = ( + 6D0991292A7AA10800DF75EC /* BlockiesIdenticonViewPreviews.swift */, + 6D749C5F2A69C6020064D7E5 /* PolkadotIdenticonViewPreviews.swift */, + ); + path = Identicons; + sourceTree = ""; + }; 6D77F31D296D0C4000044C7C /* DerivedKey */ = { isa = PBXGroup; children = ( @@ -1956,6 +1971,7 @@ 6D971ABB2941B1C600121A36 /* SVGView */, 6D971AC92942E79100121A36 /* QRCode */, 6D749C5B2A6871BA0064D7E5 /* Blockies */, + 6D749C612A69CE2C0064D7E5 /* PolkadotIdenticon */, ); productName = PolkadotVault; productReference = 2DE72BBE26A588C7002BB752 /* PolkadotVault.app */; @@ -2222,7 +2238,6 @@ 6DA2ACAC2939E87600AAEADC /* Event+Value.swift in Sources */, 6D25E6B32A02323A00376AB9 /* FullscreenModal.swift in Sources */, 6DDF439C29879D3900881AFF /* SecondaryFont.swift in Sources */, - 6D749C5A2A6871530064D7E5 /* BlockiesIdenticonViewPreviews.swift in Sources */, 6D6430F628CB460A00342E37 /* ServiceLocator.swift in Sources */, 6DC5642E28B652DE003D540B /* AddKeySetModal.swift in Sources */, 6DF91F4329C06BB4000A6BB2 /* TextResources.swift in Sources */, @@ -2234,6 +2249,7 @@ 6DEA1B1528D4587200D170B7 /* SeedPhraseView.swift in Sources */, 6D8973AB2A08EE1E0046A2F3 /* SignSpecService.swift in Sources */, 6D6430EF28CB30A000342E37 /* BottoEdgeOverlay.swift in Sources */, + 6D749C602A69C6020064D7E5 /* PolkadotIdenticonViewPreviews.swift in Sources */, 6D88CFF828C634CA001FB0A1 /* KeyDetailsActionsModal.swift in Sources */, 6DE67D05298AA4270042415A /* BackupSelectKeyView.swift in Sources */, 6D45065E296E94FC0065B4D4 /* DerivationMethodsInfoView.swift in Sources */, @@ -2435,6 +2451,7 @@ 6D042AF22901B3FB00B3F4F7 /* QRCodeImageGenerator.swift in Sources */, 6D95E97528B500EE00E28A11 /* Heights.swift in Sources */, 6D8045DE28D087D400237F8C /* QRCodeRootFooterView.swift in Sources */, + 6D09912A2A7AA10800DF75EC /* BlockiesIdenticonViewPreviews.swift in Sources */, 6DA317C1299E3DDF005DD060 /* OnboardingAirgapView.swift in Sources */, 6DFE588D297A72B1002BFDBF /* JailbreakDetectionPublisher.swift in Sources */, 6D42793728DB147800C141DC /* PrivateKeyQRCodeService.swift in Sources */, @@ -2784,6 +2801,10 @@ isa = XCSwiftPackageProductDependency; productName = Blockies; }; + 6D749C612A69CE2C0064D7E5 /* PolkadotIdenticon */ = { + isa = XCSwiftPackageProductDependency; + productName = PolkadotIdenticon; + }; 6D971ABB2941B1C600121A36 /* SVGView */ = { isa = XCSwiftPackageProductDependency; package = 6D971ABA2941B1C600121A36 /* XCRemoteSwiftPackageReference "SVGView" */; diff --git a/ios/PolkadotVault.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/PolkadotVault.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 529c756e50..2f5db31a9b 100644 --- a/ios/PolkadotVault.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ios/PolkadotVault.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt.git", + "state" : { + "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", + "version" : "5.3.0" + } + }, + { + "identity" : "blake2.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tesseract-one/Blake2.swift.git", + "state" : { + "revision" : "106f68231100021e1f745779e241f81a065ad0de", + "version" : "0.1.2" + } + }, { "identity" : "qrcode", "kind" : "remoteSourceControl", diff --git a/ios/PolkadotVault/Components/BlockiesIdenticonViewPreviews.swift b/ios/PolkadotVault/Components/Identicons/BlockiesIdenticonViewPreviews.swift similarity index 100% rename from ios/PolkadotVault/Components/BlockiesIdenticonViewPreviews.swift rename to ios/PolkadotVault/Components/Identicons/BlockiesIdenticonViewPreviews.swift diff --git a/ios/PolkadotVault/Components/Identicons/PolkadotIdenticonViewPreviews.swift b/ios/PolkadotVault/Components/Identicons/PolkadotIdenticonViewPreviews.swift new file mode 100644 index 0000000000..95fdc2d98e --- /dev/null +++ b/ios/PolkadotVault/Components/Identicons/PolkadotIdenticonViewPreviews.swift @@ -0,0 +1,31 @@ +// +// PolkadotIdenticonViewPreviews.swift +// PolkadotVault +// +// Created by Krzysztof Rodak on 02/08/2023. +// + +import PolkadotIdenticon +import SwiftUI + +struct PolkadotIdenticonView_Previews: PreviewProvider { + static var previews: some View { + VStack { + PolkadotIdenticonView( + publicKey: .hex("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"), + size: 100 + ) + PolkadotIdenticonView( + publicKey: .base58("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"), + size: 100 + ) + PolkadotIdenticonView( + publicKey: .data(Data([ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, + 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125 + ])), + size: 100 + ) + } + } +}