diff --git a/ConcordiumWallet.xcodeproj/project.pbxproj b/ConcordiumWallet.xcodeproj/project.pbxproj index 6c56239f..931757bc 100644 --- a/ConcordiumWallet.xcodeproj/project.pbxproj +++ b/ConcordiumWallet.xcodeproj/project.pbxproj @@ -417,6 +417,10 @@ 080F53A42BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; }; 080F53A52BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; }; 080F53A62BA9E38800BADD38 /* SearchTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */; }; + 08C169E02BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; }; + 08C169E12BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; }; + 08C169E22BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; }; + 08C169E32BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */; }; 08F7ABD32B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; }; 08F7ABD42B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; }; 08F7ABD52B962CCA008588D2 /* CIS2TokenSelectViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */; }; @@ -2953,6 +2957,7 @@ 07E6E27527B558A30083A852 /* intro_flow_style.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = intro_flow_style.css; sourceTree = ""; }; 07E8573326F1D24F001FB2D2 /* RealmHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmHelper.swift; sourceTree = ""; }; 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTokenViewModel.swift; sourceTree = ""; }; + 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIS2TokenImagePreview.swift; sourceTree = ""; }; 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIS2TokenSelectViewModel.swift; sourceTree = ""; }; 17A41E4A266789F10079BD3A /* StagingNet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StagingNet.entitlements; sourceTree = ""; }; 17A41E4B26678FF80079BD3A /* Concordium ID.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Concordium ID.entitlements"; sourceTree = ""; }; @@ -3722,6 +3727,7 @@ 0154A6772A8CEA0500C33954 /* CIS2TokenSelectView.swift */, 08F7ABD22B962CCA008588D2 /* CIS2TokenSelectViewModel.swift */, 080F53A22BA9E38800BADD38 /* SearchTokenViewModel.swift */, + 08C169DF2BC3C59F00E61326 /* CIS2TokenImagePreview.swift */, ); path = TokenSelectionView; sourceTree = ""; @@ -6878,6 +6884,7 @@ 89B34A0C2892ADAE0059891C /* RecoveryPhraseRecoverIntroPresenter.swift in Sources */, 075A4BBE2719A04F00066AF1 /* CopyReferenceInfoWidgetViewController.swift in Sources */, 8992BA22281FFDDB005279C9 /* BakerPoolMenuPresenter.swift in Sources */, + 08C169E02BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */, 89B34A022892AACE0059891C /* RecoveryPhraseRecoverIntroView.swift in Sources */, 017A5F242A49B97500E80B56 /* Schema.swift in Sources */, FA01CA22291C5D3100BD8738 /* CGFloat+Helper.swift in Sources */, @@ -7249,6 +7256,7 @@ FA01C9D4291C5D3100BD8738 /* UIScreen+Dimensions.swift in Sources */, FA01C9E0291C5D3100BD8738 /* UITextField+Helper.swift in Sources */, 89D46873289D21D2007D3FC8 /* SeedIdentityStatusView.swift in Sources */, + 08C169E22BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */, 7F85B774246A9A7C00ED09B8 /* AccountDetailsViewController.swift in Sources */, 7F168A0E24812B7100DE429E /* SendFundConfirmationViewController.swift in Sources */, 01A3D11A2B19CF7E004FB79D /* SendFundsAmount.swift in Sources */, @@ -7843,6 +7851,7 @@ 7F85B86F246A9AB600ED09B8 /* ShowAlertProtocol.swift in Sources */, FA01C9D5291C5D3100BD8738 /* UIScreen+Dimensions.swift in Sources */, FA01C9E1291C5D3100BD8738 /* UITextField+Helper.swift in Sources */, + 08C169E32BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */, 89D46874289D21D2007D3FC8 /* SeedIdentityStatusView.swift in Sources */, 7F168A0F24812B7100DE429E /* SendFundConfirmationViewController.swift in Sources */, 01A3D11B2B19CF7E004FB79D /* SendFundsAmount.swift in Sources */, @@ -8589,6 +8598,7 @@ 19C2EE4F9BADE3BCC1AF1358 /* SelectRecipientViewController.swift in Sources */, 079F310326EB5A2D00ED96D3 /* PermissionHelper.swift in Sources */, 19C2ED4B9B793A090B70CF7F /* AddRecipientPresenter.swift in Sources */, + 08C169E12BC3C59F00E61326 /* CIS2TokenImagePreview.swift in Sources */, 07519F6D26FB23DE00F4E6E3 /* AddMemoViewController.swift in Sources */, 01A3D11E2B19CF90004FB79D /* FungibleToken.swift in Sources */, 52CE0C7A250FFEC70018B5D1 /* Encrypted.swift in Sources */, @@ -9342,7 +9352,7 @@ repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + minimumVersion = 3.0.0; }; }; 01BCA9582AB9CF62006D1F0C /* XCRemoteSwiftPackageReference "BigInt" */ = { diff --git a/ConcordiumWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ConcordiumWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index fc11dfeb..00000000 --- a/ConcordiumWallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,149 +0,0 @@ -{ - "pins" : [ - { - "identity" : "bigint", - "kind" : "remoteSourceControl", - "location" : "https://github.com/attaswift/BigInt.git", - "state" : { - "revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6", - "version" : "5.3.0" - } - }, - { - "identity" : "concordium-wallet-crypto-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Concordium/concordium-wallet-crypto-swift", - "state" : { - "revision" : "f7728a5274aae63e1bd5204ba455dfaf740f57a9", - "version" : "0.24.0-0" - } - }, - { - "identity" : "matomo-sdk-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/matomo-org/matomo-sdk-ios.git", - "state" : { - "revision" : "15a645e11eaa8053f93749d73ced03da1e56fd01", - "version" : "7.5.2" - } - }, - { - "identity" : "mnemonicswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/zcash-hackworks/MnemonicSwift", - "state" : { - "revision" : "716a2c32ac2bbd8a1499ac834077df42b75edc85", - "version" : "2.2.4" - } - }, - { - "identity" : "qrcode", - "kind" : "remoteSourceControl", - "location" : "https://github.com/WalletConnect/QRCode", - "state" : { - "revision" : "263f280d2c8144adfb0b6676109846cfc8dd552b", - "version" : "14.3.1" - } - }, - { - "identity" : "realm-core", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-core", - "state" : { - "revision" : "f1e962cd447f8b69f8f7cf46a188b1c6246923c5", - "version" : "13.17.0" - } - }, - { - "identity" : "realm-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/realm/realm-swift", - "state" : { - "revision" : "0155caac1a0830a9fbaaffe5866b909fad3a6fc4", - "version" : "10.41.1" - } - }, - { - "identity" : "sdwebimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SDWebImage/SDWebImage.git", - "state" : { - "revision" : "633996a807442ec28df9d33b0f88ce57a0e2fdbf", - "version" : "5.17.0" - } - }, - { - "identity" : "sdwebimagesvgcoder", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SDWebImage/SDWebImageSVGCoder.git", - "state" : { - "revision" : "950167445ab703740569869c8b7510efc9d09a26", - "version" : "1.7.0" - } - }, - { - "identity" : "sdwebimageswiftui", - "kind" : "remoteSourceControl", - "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI.git", - "state" : { - "revision" : "e837c37d45449fbd3b4745c10c5b5274e73edead", - "version" : "2.2.3" - } - }, - { - "identity" : "starscream", - "kind" : "remoteSourceControl", - "location" : "https://github.com/daltoniam/Starscream.git", - "state" : { - "revision" : "a063fda2b8145a231953c20e7a646be254365396", - "version" : "3.1.2" - } - }, - { - "identity" : "swift-crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-crypto.git", - "state" : { - "revision" : "75ec60b8b4cc0f085c3ac414f3dca5625fa3588e", - "version" : "2.2.4" - } - }, - { - "identity" : "swift-qrcode-generator", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dagronf/swift-qrcode-generator", - "state" : { - "revision" : "5ca09b6a2ad190f94aa3d6ddef45b187f8c0343b", - "version" : "1.0.3" - } - }, - { - "identity" : "swiftcbor", - "kind" : "remoteSourceControl", - "location" : "https://github.com/unrelentingtech/SwiftCBOR", - "state" : { - "revision" : "98a59c305cca5c5367c3422d5e127661d4e76e24", - "version" : "0.4.5" - } - }, - { - "identity" : "swiftimagereadwrite", - "kind" : "remoteSourceControl", - "location" : "https://github.com/dagronf/SwiftImageReadWrite", - "state" : { - "revision" : "5596407d1cf61b953b8e658fa8636a471df3c509", - "version" : "1.1.6" - } - }, - { - "identity" : "walletconnectswiftv2", - "kind" : "remoteSourceControl", - "location" : "https://github.com/WalletConnect/WalletConnectSwiftV2.git", - "state" : { - "revision" : "84b09a13b48d08f1092a7494e2ad70cbb92545c2", - "version" : "1.6.11" - } - } - ], - "version" : 2 - } \ No newline at end of file diff --git a/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenDetails/TokenDetailsView.swift b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenDetails/TokenDetailsView.swift index 6b4e859d..8cd6178e 100644 --- a/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenDetails/TokenDetailsView.swift +++ b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenDetails/TokenDetailsView.swift @@ -106,13 +106,15 @@ struct TokenDetailsView: View { } HStack(alignment: .center) { let size: CGFloat = token.display == nil ? 100 : 300 - WebImage(url: token.display ?? token.thumbnail) - .resizable() - .placeholder(Image(systemName: "photo")) - .indicator(.activity) - .transition(.fade(duration: 0.5)) - .scaledToFit() - .frame(width: size, height: size, alignment: .center) + WebImage(url: token.display ?? token.thumbnail) { image in + image.resizable() + } placeholder: { + Image(systemName: "photo") + } + .indicator(.activity) + .transition(.fade(duration: 0.5)) + .scaledToFit() + .frame(width: size, height: size, alignment: .center) } .frame(maxWidth: .infinity) Group { diff --git a/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenImagePreview.swift b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenImagePreview.swift new file mode 100644 index 00000000..bdcbcce8 --- /dev/null +++ b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenImagePreview.swift @@ -0,0 +1,107 @@ +// +// CryptoImage.swift +// ConcordiumWallet +// +// Created by Maksym Rachytskyy on 08.04.2024. +// Copyright © 2024 concordium. All rights reserved. +// + +import SwiftUI +import SDWebImageSwiftUI +import SDWebImageSVGCoder + +struct CIS2TokenImagePreview: View { + enum Size { + case small + case medium + case custom(width: CGFloat, height: CGFloat) + + var size: CGSize { + switch self { + case .small: return .init(width: 20, height: 20) + case .medium: return .init(width: 45, height: 45) + case let .custom(width, height): return .init(width: width, height: height) + } + } + } + + let url: URL? + let size: CIS2TokenImagePreview.Size + + var body: some View { + Group { + if let url = url, (url.absoluteString.range(of: ".svg", options: .caseInsensitive) != nil) { + WebImage( + url: url, + context: [.imageCoder: CustomSVGDecoder(fallbackDecoder: SDImageSVGCoder.shared)] + ) + .resizable() + .indicator(.activity) + + } else { + AsyncImage(url: url, scale: 1.0) { image in + image + .resizable() + } placeholder: { + Color.gray.opacity(0.4).clipShape(Circle()) + } + } + } + .clipShape(Circle()) + .transition(.fade(duration: 0.5)) + .aspectRatio(contentMode: .fill) + .frame(width: size.size.width, height: size.size.height) + } +} + +/// https://stackoverflow.com/questions/74427783/download-svg-image-in-ios-swift +private class CustomSVGDecoder: NSObject, SDImageCoder { + + let fallbackDecoder: SDImageCoder? + + init(fallbackDecoder: SDImageCoder?) { + self.fallbackDecoder = fallbackDecoder + } + + static var regex: NSRegularExpression = { + let pattern = "" + let regex = try! NSRegularExpression(pattern: pattern, options: []) + return regex + }() + + func canDecode(from data: Data?) -> Bool { + guard let data = data, let string = String(data: data, encoding: .utf8) else { return false } + guard Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) == nil else { + return true //It self should decode + } + guard let fallbackDecoder = fallbackDecoder else { + return false + } + return fallbackDecoder.canDecode(from: data) + } + + func decodedImage(with data: Data?, options: [SDImageCoderOption : Any]? = nil) -> UIImage? { + guard let data = data, + let string = String(data: data, encoding: .utf8) else { return nil } + guard let match = Self.regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) else { + return fallbackDecoder?.decodedImage(with: data, options: options) + } + guard let rawBase64DataRange = Range(match.range(at: 2), in: string) else { + return fallbackDecoder?.decodedImage(with: data, options: options) + } + let rawBase64Data = String(string[rawBase64DataRange]) + guard let imageData = Data(base64Encoded: Data(rawBase64Data.utf8), options: .ignoreUnknownCharacters) else { + return fallbackDecoder?.decodedImage(with: data, options: options) + } + return UIImage(data: imageData) + } + + //You might need to implement these methods, I didn't check their meaning yet + func canEncode(to format: SDImageFormat) -> Bool { + return true + } + + func encodedData(with image: UIImage?, format: SDImageFormat, options: [SDImageCoderOption : Any]? = nil) -> Data? { + return nil + } +} diff --git a/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenSelectView.swift b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenSelectView.swift index 09a6cffd..3283d7a3 100644 --- a/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenSelectView.swift +++ b/ConcordiumWallet/Views/AccountsView/AccountDetails/TokenSelectionView/CIS2TokenSelectView.swift @@ -198,12 +198,7 @@ struct CIS2TokenSelectView: View { func CIS2TokenView(model: CIS2TokenSelectionRepresentable) -> some View { HStack { - WebImage(url: model.thumbnail ?? model.display) - .resizable() - .placeholder(Image(systemName: "photo")) - .indicator(.activity) - .scaledToFit() - .frame(width: 45, height: 45, alignment: .center) + CIS2TokenImagePreview(url: model.thumbnail ?? model.display, size: .medium) VStack(alignment: .leading) { Text(model.name) diff --git a/ConcordiumWallet/Views/AccountsView/SendFunds/SendFund/SendFundTokenSelection.swift b/ConcordiumWallet/Views/AccountsView/SendFunds/SendFund/SendFundTokenSelection.swift index 7f699f41..496c3ab0 100644 --- a/ConcordiumWallet/Views/AccountsView/SendFunds/SendFund/SendFundTokenSelection.swift +++ b/ConcordiumWallet/Views/AccountsView/SendFunds/SendFund/SendFundTokenSelection.swift @@ -39,13 +39,15 @@ struct SendFundTokenSelection: View { didSelectToken(token) } label: { HStack { - WebImage(url: token.thumbnail) - .resizable() - .placeholder(Image(systemName: "photo")) - .indicator(.activity) - .transition(.fade(duration: 0.5)) - .scaledToFit() - .frame(width: 48, height: 48, alignment: .center) + WebImage(url: token.thumbnail) { image in + image.resizable() + } placeholder: { + Image(systemName: "photo") + } + .indicator(.activity) + .transition(.fade(duration: 0.5)) + .scaledToFit() + .frame(width: 48, height: 48, alignment: .center) VStack(alignment: .leading) { Text(token.name) .multilineTextAlignment(.leading)