commit 00e03ddb0cc3ae1192f312c0f547097e9eeb5d17
parent 416a1650f961fc496d825e547eefb499f8781fbd
Author: Jonathan Buchanan <jonathan.russ.buchanan@gmail.com>
Date: Wed, 15 Jun 2022 14:09:48 -0400
separate common taler code into a local swift package
Diffstat:
14 files changed, 492 insertions(+), 432 deletions(-)
diff --git a/Taler.xcodeproj/project.pbxproj b/Taler.xcodeproj/project.pbxproj
@@ -7,8 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ ABC13AA32859962800D23185 /* taler-swift in Frameworks */ = {isa = PBXBuildFile; productRef = ABC13AA22859962800D23185 /* taler-swift */; };
D112510026B12E3200D02E00 /* taler-wallet-embedded.js in CopyFiles */ = {isa = PBXBuildFile; fileRef = D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */; };
- D1472E5526B9206800896566 /* AmountTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1472E5426B9206800896566 /* AmountTests.swift */; };
D14AFD2124D232B300C51073 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2024D232B300C51073 /* AppDelegate.swift */; };
D14AFD2324D232B300C51073 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD2224D232B300C51073 /* SceneDelegate.swift */; };
D14AFD3824D232B500C51073 /* TalerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D14AFD3724D232B500C51073 /* TalerTests.swift */; };
@@ -37,7 +37,6 @@
D17D8B8525ADB29B001BD43D /* libcares.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D17D8B4825ADB12B001BD43D /* libcares.a */; };
D18DBB5E26DF160D00A4480D /* TimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D18DBB5D26DF160D00A4480D /* TimestampTests.swift */; };
D1AFF0F3268D59C200FBB744 /* libiono.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1AFF0F2268D59A500FBB744 /* libiono.a */; };
- D1BA3F9226B8889600A5848B /* Amount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BA3F9126B8889600A5848B /* Amount.swift */; };
D1D65B9826992E4600C1012A /* WalletBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D65B9726992E4600C1012A /* WalletBackend.swift */; };
/* End PBXBuildFile section */
@@ -72,6 +71,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ AB710490285995B6008B04F0 /* taler-swift */ = {isa = PBXFileReference; lastKnownFileType = text; path = "taler-swift"; sourceTree = SOURCE_ROOT; };
D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "taler-wallet-embedded.js"; sourceTree = "<group>"; };
D11DB44E25A5C487009CF0BC /* libnode.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libnode.a; path = "nodejs-mobile/out/Release/libnode.a"; sourceTree = "<group>"; };
D11DB45625A5C5C7009CF0BC /* libv8_initializers.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_initializers.a; path = "nodejs-mobile/out/Release/libv8_initializers.a"; sourceTree = "<group>"; };
@@ -115,7 +115,6 @@
D145D1EF25AC416B00CDD61B /* libv8_base_without_compiler.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_base_without_compiler.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libv8_base_without_compiler.a"; sourceTree = "<group>"; };
D145D1F025AC416B00CDD61B /* libhistogram.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libhistogram.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libhistogram.a"; sourceTree = "<group>"; };
D145D1F125AC416B00CDD61B /* libv8_libbase.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_libbase.a; path = "ios-node-v8/taler-ios-build/compiled/node-x64/libv8_libbase.a"; sourceTree = "<group>"; };
- D1472E5426B9206800896566 /* AmountTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AmountTests.swift; sourceTree = "<group>"; };
D14AFD1D24D232B300C51073 /* Taler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Taler.app; sourceTree = BUILT_PRODUCTS_DIR; };
D14AFD2024D232B300C51073 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
D14AFD2224D232B300C51073 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@@ -155,7 +154,6 @@
D18DBB5D26DF160D00A4480D /* TimestampTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampTests.swift; sourceTree = "<group>"; };
D1AB963B259EB13D00DEAB23 /* libnode.89.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libnode.89.dylib; path = "ios-node-v8/taler-ios-build/compiled/x64-v8a/libnode.89.dylib"; sourceTree = "<group>"; };
D1AFF0F2268D59A500FBB744 /* libiono.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libiono.a; path = iono/compiled/x64/libiono.a; sourceTree = "<group>"; };
- D1BA3F9126B8889600A5848B /* Amount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Amount.swift; sourceTree = "<group>"; };
D1D65B9726992E4600C1012A /* WalletBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletBackend.swift; sourceTree = "<group>"; };
D1F0C22F25A958AE00C3179D /* libllhttp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libllhttp.a; path = "ios-node-v8/tools/ios-framework/bin/x64/libllhttp.a"; sourceTree = "<group>"; };
D1F0C23025A958AE00C3179D /* libv8_initializers.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libv8_initializers.a; path = "ios-node-v8/tools/ios-framework/bin/x64/libv8_initializers.a"; sourceTree = "<group>"; };
@@ -218,6 +216,7 @@
D17D8B7B25ADB29B001BD43D /* libv8_compiler.a in Frameworks */,
D17D8B7225ADB29A001BD43D /* libbrotli.a in Frameworks */,
D17D8B7625ADB29A001BD43D /* libv8_libsampler.a in Frameworks */,
+ ABC13AA32859962800D23185 /* taler-swift in Frameworks */,
D17D8B7825ADB29B001BD43D /* libv8_libbase.a in Frameworks */,
D17D8B7C25ADB29B001BD43D /* libv8_base_without_compiler.a in Frameworks */,
D17D8B7725ADB29A001BD43D /* libv8_libplatform.a in Frameworks */,
@@ -249,6 +248,7 @@
D14AFD1424D232B300C51073 = {
isa = PBXGroup;
children = (
+ AB710490285995B6008B04F0 /* taler-swift */,
D11250FF26B12E3200D02E00 /* taler-wallet-embedded.js */,
D14AFD1F24D232B300C51073 /* Taler */,
D14AFD3624D232B500C51073 /* TalerTests */,
@@ -274,7 +274,6 @@
D14AFD2024D232B300C51073 /* AppDelegate.swift */,
D14AFD2224D232B300C51073 /* SceneDelegate.swift */,
D1D65B9726992E4600C1012A /* WalletBackend.swift */,
- D1BA3F9126B8889600A5848B /* Amount.swift */,
D14CE1B126C39E5D00612DBE /* BalanceRow.swift */,
D14CE1B326C3A2D400612DBE /* BalanceList.swift */,
D14AFD2624D232B500C51073 /* Assets.xcassets */,
@@ -289,7 +288,6 @@
children = (
D14AFD3724D232B500C51073 /* TalerTests.swift */,
D14AFD3924D232B500C51073 /* Info.plist */,
- D1472E5426B9206800896566 /* AmountTests.swift */,
D18DBB5D26DF160D00A4480D /* TimestampTests.swift */,
);
path = TalerTests;
@@ -436,6 +434,9 @@
dependencies = (
);
name = Taler;
+ packageProductDependencies = (
+ ABC13AA22859962800D23185 /* taler-swift */,
+ );
productName = Taler;
productReference = D14AFD1D24D232B300C51073 /* Taler.app */;
productType = "com.apple.product-type.application";
@@ -591,7 +592,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- D1BA3F9226B8889600A5848B /* Amount.swift in Sources */,
D14AFD2124D232B300C51073 /* AppDelegate.swift in Sources */,
D14AFD2324D232B300C51073 /* SceneDelegate.swift in Sources */,
D1D65B9826992E4600C1012A /* WalletBackend.swift in Sources */,
@@ -605,7 +605,6 @@
buildActionMask = 2147483647;
files = (
D14AFD3824D232B500C51073 /* TalerTests.swift in Sources */,
- D1472E5526B9206800896566 /* AmountTests.swift in Sources */,
D18DBB5E26DF160D00A4480D /* TimestampTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -986,6 +985,13 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ ABC13AA22859962800D23185 /* taler-swift */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = "taler-swift";
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = D14AFD1524D232B300C51073 /* Project object */;
}
diff --git a/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate b/Taler.xcodeproj/project.xcworkspace/xcuserdata/jonathan.xcuserdatad/UserInterfaceState.xcuserstate
Binary files differ.
diff --git a/Taler/Amount.swift b/Taler/Amount.swift
@@ -1,410 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2021 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import Foundation
-
-/**
- Errors for `Amount`.
- */
-enum AmountError: Error {
- /**
- The string cannot be parsed to create an `Amount`.
- */
- case invalidStringRepresentation
-
- /**
- Could not compare or operate on two `Amount`s of different currencies.
- */
- case incompatibleCurrency
-
- /**
- The amount is invalid. The value is either greater than the maximum, or the currency is the empty string.
- */
- case invalidAmount
-
- /**
- The result of the operation would yield a negative amount.
- */
- case negativeAmount
-
- /**
- The operation was division by zero.
- */
- case divideByZero
-}
-
-/**
- A value of a currency.
- */
-class Amount: Codable, CustomStringConvertible {
- /**
- The largest possible value that can be represented.
- */
- private static let maxValue: UInt64 = 1 << 52
-
- /**
- The size of `value` in relation to `fraction`.
- */
- private static let fractionalBase: UInt32 = 100000000
-
- /**
- The greatest number of decimal digits that can be represented.
- */
- private static let fractionalBaseDigits: UInt = 8
-
- /**
- The currency of the amount.
- */
- var currency: String
-
- /**
- The value of the amount (number to the left of the decimal point).
- */
- var value: UInt64
-
- /**
- The fractional value of the amount (number to the right of the decimal point).
- */
- var fraction: UInt32
-
- /**
- The string representation of the amount, formatted as "`currency`:`value`.`fraction`".
- */
- var description: String {
- if fraction == 0 {
- return "\(currency):\(value)"
- } else {
- var frac = fraction
- var fracStr = ""
- while (frac > 0) {
- fracStr += "\(frac / (Amount.fractionalBase / 10))"
- frac = (frac * 10) % Amount.fractionalBase
- }
- return "\(currency):\(value).\(fracStr)"
- }
- }
-
- /**
- Whether the value is valid. An amount is valid if and only if the currency is not empty and the value is less than the maximum allowed value.
- */
- var valid: Bool {
- return (value <= Amount.maxValue && currency != "")
- }
-
- /**
- Initializes an amount by parsing a string representing the amount. The string should be formatted as "`currency`:`value`.`fraction`".
-
- - Parameters:
- - fromString: The string to parse.
-
- - Throws:
- - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
- - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
- */
- init(fromString string: String) throws {
- guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
- self.currency = String(string[..<separatorIndex])
- let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
- if let dotIndex = amountStr.firstIndex(of: ".") {
- let valueStr = String(amountStr[..<dotIndex])
- let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
- guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
- self.value = _value
- self.fraction = 0
- var digitValue = Amount.fractionalBase / 10
- for char in fractionStr {
- guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
- self.fraction += digitValue * UInt32(digit)
- digitValue /= 10
- }
- } else {
- guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
- self.value = _value
- self.fraction = 0
- }
- guard self.valid else { throw AmountError.invalidAmount }
- }
-
- /**
- Initializes an amount with the specified currency, value, and fraction.
-
- - Parameters:
- - currency: The currency of the amount.
- - value: The value of the amount (number to the left of the decimal point).
- - fraction: The fractional value of the amount (number to the right of the decimal point).
- */
- init(currency: String, value: UInt64, fraction: UInt32) {
- self.currency = currency
- self.value = value
- self.fraction = fraction
- }
-
- /**
- Initializes an amount from a decoder.
-
- - Parameters:
- - fromDecoder: The decoder to extract the amount from.
-
- - Throws:
- - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
- - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
- */
- init(fromDecoder decoder: Decoder) throws {
- let container = try decoder.singleValueContainer()
- let string = try container.decode(String.self)
- /* TODO: de-duplicate */
- guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
- self.currency = String(string[..<separatorIndex])
- let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
- if let dotIndex = amountStr.firstIndex(of: ".") {
- let valueStr = String(amountStr[..<dotIndex])
- let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
- guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
- self.value = _value
- self.fraction = 0
- var digitValue = Amount.fractionalBase / 10
- for char in fractionStr {
- guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
- self.fraction += digitValue * UInt32(digit)
- digitValue /= 10
- }
- } else {
- guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
- self.value = _value
- self.fraction = 0
- }
- guard self.valid else { throw AmountError.invalidAmount }
- }
-
- /**
- Copies an amount.
-
- - Returns: A copy of the amount.
- */
- func copy() -> Amount {
- return Amount(currency: self.currency, value: self.value, fraction: self.fraction)
- }
-
- /**
- Creates a normalized copy of an amount.
-
- - Returns: A copy of the amount that has been normalized
- */
- func normalizedCopy() throws -> Amount {
- let amount = self.copy()
- try amount.normalize()
- return amount
- }
-
- /**
- Encodes an amount.
-
- - Parameters:
- - to: The encoder to encode the amount with.
- */
- func encode(to encoder: Encoder) throws {
- var container = encoder.singleValueContainer()
- try container.encode(self.description)
- }
-
- /**
- Normalizes an amount by reducing `fraction` until it is less than `Amount.fractionalBase`, increasing `value` appropriately.
-
- - Throws:
- - `AmountError.invalidAmount` if the amount is invalid either before or after normalization.
- */
- func normalize() throws {
- if !valid {
- throw AmountError.invalidAmount
- }
- self.value += UInt64(self.fraction / Amount.fractionalBase)
- self.fraction = self.fraction % Amount.fractionalBase
- if !valid {
- throw AmountError.invalidAmount
- }
- }
-
- /**
- Adds two amounts together.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: The sum of `left` and `right`, normalized.
- */
- static func + (left: Amount, right: Amount) throws -> Amount {
- if left.currency != right.currency {
- throw AmountError.incompatibleCurrency
- }
- let leftNormalized = try left.normalizedCopy()
- let rightNormalized = try right.normalizedCopy()
- let result: Amount = leftNormalized
- result.value += rightNormalized.value
- result.fraction += rightNormalized.fraction
- try result.normalize()
- return result
- }
-
- /**
- Subtracts one amount from another.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: The difference of `left` and `right`, normalized.
- */
- static func - (left: Amount, right: Amount) throws -> Amount {
- if left.currency != right.currency {
- throw AmountError.incompatibleCurrency
- }
- let leftNormalized = try left.normalizedCopy()
- let rightNormalized = try right.normalizedCopy()
- if (leftNormalized.fraction < rightNormalized.fraction) {
- guard leftNormalized.value != 0 else { throw AmountError.negativeAmount }
- leftNormalized.value -= 1
- leftNormalized.fraction += Amount.fractionalBase
- }
- guard leftNormalized.value >= rightNormalized.value else { throw AmountError.negativeAmount }
- let diff = Amount.zero(currency: left.currency)
- diff.value = leftNormalized.value - rightNormalized.value
- diff.fraction = leftNormalized.fraction - rightNormalized.fraction
- try diff.normalize()
- return diff
- }
-
- /**
- Divides an amount by a scalar, possibly introducing rounding error.
-
- - Parameters:
- - dividend: The amount to divide.
- - divisor: The scalar dividing `dividend`.
-
- - Returns: The quotient of `dividend` and `divisor`, normalized.
- */
- static func / (dividend: Amount, divisor: UInt32) throws -> Amount {
- guard divisor != 0 else { throw AmountError.divideByZero }
- let result = try dividend.normalizedCopy()
- if (divisor == 1) {
- return result
- }
- var remainder = result.value % UInt64(divisor)
- result.value = result.value / UInt64(divisor)
- remainder = (remainder * UInt64(Amount.fractionalBase)) + UInt64(result.fraction)
- result.fraction = UInt32(remainder) / divisor
- try result.normalize()
- return result
- }
-
- /**
- Multiply an amount by a scalar.
-
- - Parameters:
- - amount: The amount to multiply.
- - factor: The scalar multiplying `amount`.
-
- - Returns: The product of `amount` and `factor`, normalized.
- */
- static func * (amount: Amount, factor: UInt32) throws -> Amount {
- let result = try amount.normalizedCopy()
- result.value = result.value * UInt64(factor)
- let fraction_tmp = UInt64(result.fraction) * UInt64(factor)
- result.value += fraction_tmp / UInt64(Amount.fractionalBase)
- result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase))
- return result
- }
-
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The first amount.
- - right: The second amount.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if the amounts have the same `value` and `fraction` after normalization, `false` otherwise.
- */
- static func == (left: Amount, right: Amount) throws -> Bool {
- if left.currency != right.currency {
- throw AmountError.incompatibleCurrency
- }
- let leftNormalized = try left.normalizedCopy()
- let rightNormalized = try right.normalizedCopy()
- return (leftNormalized.value == rightNormalized.value && leftNormalized.fraction == rightNormalized.fraction)
- }
-
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if `left` is smaller than `right` after normalization, `false` otherwise.
- */
- static func < (left: Amount, right: Amount) throws -> Bool {
- if left.currency != right.currency {
- throw AmountError.incompatibleCurrency
- }
- let leftNormalized = try left.normalizedCopy()
- let rightNormalized = try right.normalizedCopy()
- if (leftNormalized.value == rightNormalized.value) {
- return (leftNormalized.fraction < rightNormalized.fraction)
- } else {
- return (leftNormalized.value < rightNormalized.value)
- }
- }
-
- /**
- Compares two amounts.
-
- - Parameters:
- - left: The amount on the left.
- - right: The amount on the right.
-
- - Throws:
- - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
-
- - Returns: `true` if and only if `left` is bigger than `right` after normalization, `false` otherwise.
- */
- static func > (left: Amount, right: Amount) throws -> Bool {
- return try right < left
- }
-
- /**
- Creates the amount representing zero in a given currency.
-
- - Parameters:
- - currency: The currency to use.
-
- - Returns: The zero amount for `currency`.
- */
- static func zero(currency: String) -> Amount {
- return Amount(currency: currency, value: 0, fraction: 0)
- }
-}
diff --git a/Taler/AppDelegate.swift b/Taler/AppDelegate.swift
@@ -24,7 +24,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
- let backend = WalletBackend()
+ do {
+ let backend = try WalletBackend()
+ } catch {
+
+ }
return true
}
diff --git a/Taler/BalanceList.swift b/Taler/BalanceList.swift
@@ -16,7 +16,7 @@
import SwiftUI
-struct IdentifiedArray<T>: RandomAccessCollection {
+/*struct IdentifiedArray<T>: RandomAccessCollection {
struct Item {
var id: Int
var item: T
@@ -73,4 +73,4 @@ struct BalanceList_Previews: PreviewProvider {
Balance(available: Amount(fromString: "EUR:0.02"), pendingIncoming: Amount(fromString: "EUR:0.01"), pendingOutgoing: Amount(fromString: "EUR:0.03"), requiresUserInput: false)
]))
}
-}
+}*/
diff --git a/Taler/BalanceRow.swift b/Taler/BalanceRow.swift
@@ -15,6 +15,7 @@
*/
import SwiftUI
+import taler_swift
struct BalanceRow: View {
var balance: Balance
diff --git a/Taler/WalletBackend.swift b/Taler/WalletBackend.swift
@@ -16,6 +16,7 @@
import Foundation
import iono
+import taler_swift
enum WalletBackendResponseError: Error {
case malformedResponse
@@ -1304,6 +1305,7 @@ class WalletBackendSuspendCoinRequest: WalletBackendRequest {
}
enum WalletBackendError: Error {
+ case initializationError
case serializationError
case deserializationError
}
@@ -1319,7 +1321,7 @@ class WalletBackend: IonoMessageHandler {
}
private var requests: [UInt : RequestDetail] = [:]
- init() {
+ init() throws {
iono = Iono()
requestsMade = 0
self.backendReady = false
@@ -1335,7 +1337,7 @@ class WalletBackend: IonoMessageHandler {
iono.evalNodeCode(source: "tw = require('@gnu-taler/taler-wallet-embedded');")
iono.evalNodeCode(source: "tw.installNativeWalletListener();")
} catch {
-
+ throw WalletBackendError.initializationError
}
// Send the init message
@@ -1351,12 +1353,6 @@ class WalletBackend: IonoMessageHandler {
}
waitUntilReady()
- //try! sendRequest(request: WalletBackendWithdrawTestBalance(amount: "TESTKUDOS:10", bankBaseUrl: "https://bank.test.taler.net/", exchangeBaseUrl: "https://exchange.test.taler.net/"))
- try! sendRequest(request: WalletBackendGetBalancesRequest(onSuccess: { ([Balance]) -> Void in
-
- }, onFailure: { () -> Void in
-
- }))
}
func waitUntilReady() {
diff --git a/bootstrap b/bootstrap
@@ -1,10 +1,9 @@
#!/bin/sh
-# Bootstrap the repository. Used when the repository is checked out from git.
-# When using the source tarball, running this script is not necessary.
-
set -eu
+curl https://git.taler.net/wallet-core.git/plain/v0.9.0-dev.12/taler-wallet-embedded.js?h=prebuilt --output taler-wallet-embedded.js
+
if ! git --version >/dev/null; then
echo "git not installed"
exit 1
diff --git a/taler-swift/.gitignore b/taler-swift/.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/taler-swift/Package.swift b/taler-swift/Package.swift
@@ -0,0 +1,28 @@
+// swift-tools-version: 5.6
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "taler-swift",
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "taler-swift",
+ targets: ["taler-swift"]),
+ ],
+ 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: "taler-swift",
+ dependencies: []),
+ .testTarget(
+ name: "taler-swiftTests",
+ dependencies: ["taler-swift"]),
+ ]
+)
diff --git a/taler-swift/Sources/taler-swift/Amount.swift b/taler-swift/Sources/taler-swift/Amount.swift
@@ -0,0 +1,410 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2021 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import Foundation
+
+/**
+ Errors for `Amount`.
+ */
+enum AmountError: Error {
+ /**
+ The string cannot be parsed to create an `Amount`.
+ */
+ case invalidStringRepresentation
+
+ /**
+ Could not compare or operate on two `Amount`s of different currencies.
+ */
+ case incompatibleCurrency
+
+ /**
+ The amount is invalid. The value is either greater than the maximum, or the currency is the empty string.
+ */
+ case invalidAmount
+
+ /**
+ The result of the operation would yield a negative amount.
+ */
+ case negativeAmount
+
+ /**
+ The operation was division by zero.
+ */
+ case divideByZero
+}
+
+/**
+ A value of a currency.
+ */
+public class Amount: Codable, CustomStringConvertible {
+ /**
+ The largest possible value that can be represented.
+ */
+ private static let maxValue: UInt64 = 1 << 52
+
+ /**
+ The size of `value` in relation to `fraction`.
+ */
+ private static let fractionalBase: UInt32 = 100000000
+
+ /**
+ The greatest number of decimal digits that can be represented.
+ */
+ private static let fractionalBaseDigits: UInt = 8
+
+ /**
+ The currency of the amount.
+ */
+ var currency: String
+
+ /**
+ The value of the amount (number to the left of the decimal point).
+ */
+ var value: UInt64
+
+ /**
+ The fractional value of the amount (number to the right of the decimal point).
+ */
+ var fraction: UInt32
+
+ /**
+ The string representation of the amount, formatted as "`currency`:`value`.`fraction`".
+ */
+ public var description: String {
+ if fraction == 0 {
+ return "\(currency):\(value)"
+ } else {
+ var frac = fraction
+ var fracStr = ""
+ while (frac > 0) {
+ fracStr += "\(frac / (Amount.fractionalBase / 10))"
+ frac = (frac * 10) % Amount.fractionalBase
+ }
+ return "\(currency):\(value).\(fracStr)"
+ }
+ }
+
+ /**
+ Whether the value is valid. An amount is valid if and only if the currency is not empty and the value is less than the maximum allowed value.
+ */
+ var valid: Bool {
+ return (value <= Amount.maxValue && currency != "")
+ }
+
+ /**
+ Initializes an amount by parsing a string representing the amount. The string should be formatted as "`currency`:`value`.`fraction`".
+
+ - Parameters:
+ - fromString: The string to parse.
+
+ - Throws:
+ - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
+ - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
+ */
+ public init(fromString string: String) throws {
+ guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
+ self.currency = String(string[..<separatorIndex])
+ let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
+ if let dotIndex = amountStr.firstIndex(of: ".") {
+ let valueStr = String(amountStr[..<dotIndex])
+ let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
+ guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ var digitValue = Amount.fractionalBase / 10
+ for char in fractionStr {
+ guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
+ self.fraction += digitValue * UInt32(digit)
+ digitValue /= 10
+ }
+ } else {
+ guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ }
+ guard self.valid else { throw AmountError.invalidAmount }
+ }
+
+ /**
+ Initializes an amount with the specified currency, value, and fraction.
+
+ - Parameters:
+ - currency: The currency of the amount.
+ - value: The value of the amount (number to the left of the decimal point).
+ - fraction: The fractional value of the amount (number to the right of the decimal point).
+ */
+ init(currency: String, value: UInt64, fraction: UInt32) {
+ self.currency = currency
+ self.value = value
+ self.fraction = fraction
+ }
+
+ /**
+ Initializes an amount from a decoder.
+
+ - Parameters:
+ - fromDecoder: The decoder to extract the amount from.
+
+ - Throws:
+ - `AmountError.invalidStringRepresentation` if the string cannot be parsed.
+ - `AmountError.invalidAmount` if the string can be parsed, but the resulting amount is not valid.
+ */
+ init(fromDecoder decoder: Decoder) throws {
+ let container = try decoder.singleValueContainer()
+ let string = try container.decode(String.self)
+ /* TODO: de-duplicate */
+ guard let separatorIndex = string.firstIndex(of: ":") else { throw AmountError.invalidStringRepresentation }
+ self.currency = String(string[..<separatorIndex])
+ let amountStr = String(string[string.index(separatorIndex, offsetBy: 1)...])
+ if let dotIndex = amountStr.firstIndex(of: ".") {
+ let valueStr = String(amountStr[..<dotIndex])
+ let fractionStr = String(amountStr[string.index(dotIndex, offsetBy: 1)...])
+ guard let _value = UInt64(valueStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ var digitValue = Amount.fractionalBase / 10
+ for char in fractionStr {
+ guard let digit = char.wholeNumberValue else { throw AmountError.invalidStringRepresentation }
+ self.fraction += digitValue * UInt32(digit)
+ digitValue /= 10
+ }
+ } else {
+ guard let _value = UInt64(amountStr) else { throw AmountError.invalidStringRepresentation }
+ self.value = _value
+ self.fraction = 0
+ }
+ guard self.valid else { throw AmountError.invalidAmount }
+ }
+
+ /**
+ Copies an amount.
+
+ - Returns: A copy of the amount.
+ */
+ func copy() -> Amount {
+ return Amount(currency: self.currency, value: self.value, fraction: self.fraction)
+ }
+
+ /**
+ Creates a normalized copy of an amount.
+
+ - Returns: A copy of the amount that has been normalized
+ */
+ func normalizedCopy() throws -> Amount {
+ let amount = self.copy()
+ try amount.normalize()
+ return amount
+ }
+
+ /**
+ Encodes an amount.
+
+ - Parameters:
+ - to: The encoder to encode the amount with.
+ */
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ try container.encode(self.description)
+ }
+
+ /**
+ Normalizes an amount by reducing `fraction` until it is less than `Amount.fractionalBase`, increasing `value` appropriately.
+
+ - Throws:
+ - `AmountError.invalidAmount` if the amount is invalid either before or after normalization.
+ */
+ func normalize() throws {
+ if !valid {
+ throw AmountError.invalidAmount
+ }
+ self.value += UInt64(self.fraction / Amount.fractionalBase)
+ self.fraction = self.fraction % Amount.fractionalBase
+ if !valid {
+ throw AmountError.invalidAmount
+ }
+ }
+
+ /**
+ Adds two amounts together.
+
+ - Parameters:
+ - left: The amount on the left.
+ - right: The amount on the right.
+
+ - Throws:
+ - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+
+ - Returns: The sum of `left` and `right`, normalized.
+ */
+ static func + (left: Amount, right: Amount) throws -> Amount {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ let result: Amount = leftNormalized
+ result.value += rightNormalized.value
+ result.fraction += rightNormalized.fraction
+ try result.normalize()
+ return result
+ }
+
+ /**
+ Subtracts one amount from another.
+
+ - Parameters:
+ - left: The amount on the left.
+ - right: The amount on the right.
+
+ - Throws:
+ - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+
+ - Returns: The difference of `left` and `right`, normalized.
+ */
+ static func - (left: Amount, right: Amount) throws -> Amount {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ if (leftNormalized.fraction < rightNormalized.fraction) {
+ guard leftNormalized.value != 0 else { throw AmountError.negativeAmount }
+ leftNormalized.value -= 1
+ leftNormalized.fraction += Amount.fractionalBase
+ }
+ guard leftNormalized.value >= rightNormalized.value else { throw AmountError.negativeAmount }
+ let diff = Amount.zero(currency: left.currency)
+ diff.value = leftNormalized.value - rightNormalized.value
+ diff.fraction = leftNormalized.fraction - rightNormalized.fraction
+ try diff.normalize()
+ return diff
+ }
+
+ /**
+ Divides an amount by a scalar, possibly introducing rounding error.
+
+ - Parameters:
+ - dividend: The amount to divide.
+ - divisor: The scalar dividing `dividend`.
+
+ - Returns: The quotient of `dividend` and `divisor`, normalized.
+ */
+ static func / (dividend: Amount, divisor: UInt32) throws -> Amount {
+ guard divisor != 0 else { throw AmountError.divideByZero }
+ let result = try dividend.normalizedCopy()
+ if (divisor == 1) {
+ return result
+ }
+ var remainder = result.value % UInt64(divisor)
+ result.value = result.value / UInt64(divisor)
+ remainder = (remainder * UInt64(Amount.fractionalBase)) + UInt64(result.fraction)
+ result.fraction = UInt32(remainder) / divisor
+ try result.normalize()
+ return result
+ }
+
+ /**
+ Multiply an amount by a scalar.
+
+ - Parameters:
+ - amount: The amount to multiply.
+ - factor: The scalar multiplying `amount`.
+
+ - Returns: The product of `amount` and `factor`, normalized.
+ */
+ static func * (amount: Amount, factor: UInt32) throws -> Amount {
+ let result = try amount.normalizedCopy()
+ result.value = result.value * UInt64(factor)
+ let fraction_tmp = UInt64(result.fraction) * UInt64(factor)
+ result.value += fraction_tmp / UInt64(Amount.fractionalBase)
+ result.fraction = UInt32(fraction_tmp % UInt64(Amount.fractionalBase))
+ return result
+ }
+
+ /**
+ Compares two amounts.
+
+ - Parameters:
+ - left: The first amount.
+ - right: The second amount.
+
+ - Throws:
+ - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+
+ - Returns: `true` if and only if the amounts have the same `value` and `fraction` after normalization, `false` otherwise.
+ */
+ static func == (left: Amount, right: Amount) throws -> Bool {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ return (leftNormalized.value == rightNormalized.value && leftNormalized.fraction == rightNormalized.fraction)
+ }
+
+ /**
+ Compares two amounts.
+
+ - Parameters:
+ - left: The amount on the left.
+ - right: The amount on the right.
+
+ - Throws:
+ - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+
+ - Returns: `true` if and only if `left` is smaller than `right` after normalization, `false` otherwise.
+ */
+ static func < (left: Amount, right: Amount) throws -> Bool {
+ if left.currency != right.currency {
+ throw AmountError.incompatibleCurrency
+ }
+ let leftNormalized = try left.normalizedCopy()
+ let rightNormalized = try right.normalizedCopy()
+ if (leftNormalized.value == rightNormalized.value) {
+ return (leftNormalized.fraction < rightNormalized.fraction)
+ } else {
+ return (leftNormalized.value < rightNormalized.value)
+ }
+ }
+
+ /**
+ Compares two amounts.
+
+ - Parameters:
+ - left: The amount on the left.
+ - right: The amount on the right.
+
+ - Throws:
+ - `AmountError.incompatibleCurrency` if `left` and `right` do not share the same currency.
+
+ - Returns: `true` if and only if `left` is bigger than `right` after normalization, `false` otherwise.
+ */
+ static func > (left: Amount, right: Amount) throws -> Bool {
+ return try right < left
+ }
+
+ /**
+ Creates the amount representing zero in a given currency.
+
+ - Parameters:
+ - currency: The currency to use.
+
+ - Returns: The zero amount for `currency`.
+ */
+ static func zero(currency: String) -> Amount {
+ return Amount(currency: currency, value: 0, fraction: 0)
+ }
+}
diff --git a/taler-swift/Sources/taler-swift/taler_swift.swift b/taler-swift/Sources/taler-swift/taler_swift.swift
@@ -0,0 +1,6 @@
+public struct taler_swift {
+ public private(set) var text = "Hello, World!"
+
+ public init() {
+ }
+}
diff --git a/TalerTests/AmountTests.swift b/taler-swift/Tests/taler-swiftTests/AmountTests.swift
diff --git a/taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift b/taler-swift/Tests/taler-swiftTests/taler_swiftTests.swift
@@ -0,0 +1,11 @@
+import XCTest
+@testable import taler_swift
+
+final class taler_swiftTests: XCTestCase {
+ func testExample() throws {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct
+ // results.
+ XCTAssertEqual(taler_swift().text, "Hello, World!")
+ }
+}