Skip to content

Commit

Permalink
Deep links encoding fix (#928)
Browse files Browse the repository at this point in the history
- Fixed issues related with deeplink handling. How QRs, links, and BLE data are handled in the same way.
- External contacts without given name or family name will be filter out and no longer shown on the contact book screen
  • Loading branch information
TruszczynskiA authored May 29, 2023
1 parent 1e81bcd commit c89dc7b
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 66 deletions.
8 changes: 6 additions & 2 deletions MobileWallet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@
546B03262983E92700DBED8E /* ContentNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03252983E92700DBED8E /* ContentNavigationViewController.swift */; };
546B03282983F2F700DBED8E /* OnboardingPagerElementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03272983F2F700DBED8E /* OnboardingPagerElementView.swift */; };
546B032A2983F33600DBED8E /* OnboardingPagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B03292983F33600DBED8E /* OnboardingPagerView.swift */; };
546B270D2A2482C100FE24AA /* DeepLinkDefaultActionsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546B270C2A2482C100FE24AA /* DeepLinkDefaultActionsHandler.swift */; };
546CE7F429AF514A00264699 /* FormOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F329AF514A00264699 /* FormOverlay.swift */; };
546CE7F629AF517900264699 /* FormOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F529AF517900264699 /* FormOverlayView.swift */; };
546CE7F929AF523F00264699 /* ContactBookFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546CE7F829AF523F00264699 /* ContactBookFormView.swift */; };
Expand Down Expand Up @@ -949,6 +950,7 @@
546B03252983E92700DBED8E /* ContentNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentNavigationViewController.swift; sourceTree = "<group>"; };
546B03272983F2F700DBED8E /* OnboardingPagerElementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPagerElementView.swift; sourceTree = "<group>"; };
546B03292983F33600DBED8E /* OnboardingPagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPagerView.swift; sourceTree = "<group>"; };
546B270C2A2482C100FE24AA /* DeepLinkDefaultActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkDefaultActionsHandler.swift; sourceTree = "<group>"; };
546CE7F329AF514A00264699 /* FormOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlay.swift; sourceTree = "<group>"; };
546CE7F529AF517900264699 /* FormOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormOverlayView.swift; sourceTree = "<group>"; };
546CE7F829AF523F00264699 /* ContactBookFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactBookFormView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2330,6 +2332,7 @@
3AF97E6327CF766F00FF6A3F /* Models */,
3AF97E6A27CF77B800FF6A3F /* Data Handling */,
3AF97E6827CF76C800FF6A3F /* DeeplinkHandler.swift */,
546B270C2A2482C100FE24AA /* DeepLinkDefaultActionsHandler.swift */,
);
path = "Deep Links";
sourceTree = "<group>";
Expand Down Expand Up @@ -3313,6 +3316,7 @@
3A8473CE28EC56480015E63A /* TariUTXOsService.swift in Sources */,
54CB354429B905AA00551D5A /* PopPresenter+ContactBook.swift in Sources */,
3A4CE32C26A18D7300ECF460 /* UserDefault.swift in Sources */,
546B270D2A2482C100FE24AA /* DeepLinkDefaultActionsHandler.swift in Sources */,
3A1D8E4B28400AB8004129D5 /* AboutViewHeader.swift in Sources */,
54A95B3C29747C19003CCE5C /* TariUnspentOutputsService.swift in Sources */,
3AD1306827D6290A003EC7FA /* SeedWordsListViewController.swift in Sources */,
Expand Down Expand Up @@ -3716,7 +3720,7 @@
"$(inherited)",
"$(PROJECT_DIR)/MobileWallet/TariLib",
);
MARKETING_VERSION = 0.21.0;
MARKETING_VERSION = 0.21.1;
PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet;
PRODUCT_NAME = "Tari Aurora";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -3751,7 +3755,7 @@
"$(inherited)",
"$(PROJECT_DIR)/MobileWallet/TariLib",
);
MARKETING_VERSION = 0.21.0;
MARKETING_VERSION = 0.21.1;
PRODUCT_BUNDLE_IDENTIFIER = com.tari.wallet;
PRODUCT_NAME = "Tari Aurora";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
99 changes: 99 additions & 0 deletions MobileWallet/Common/Deep Links/DeepLinkDefaultActionsHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// DeepLinkDefaultActionsHandler.swift

/*
Package MobileWallet
Created by Adrian Truszczyński on 29/05/2023
Using Swift 5.0
Running on macOS 13.0
Copyright 2019 The Tari Project
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of
its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

enum DeepLinkDefaultActionsHandler {

static func handleInForeground(contactListDeeplink: ContactListDeeplink) async throws -> Bool {
try await Task.sleep(nanoseconds: 500000000) // FIXME: Replace it with App state handler
guard await showAddContactsDialog(deeplink: contactListDeeplink) else { return false }
try addContacts(deeplink: contactListDeeplink)
return true
}

private static func showAddContactsDialog(deeplink: ContactListDeeplink) async -> Bool {

let contactCount = deeplink.list.count
let isPlural = contactCount > 1

let title = isPlural ? localized("contacts_received.popup.title.plural") : localized("contacts_received.popup.title.singular")
let messagePart2 = isPlural ? localized("contacts_received.popup.message.part.2.plural.bold", arguments: contactCount) : localized("contacts_received.popup.message.part.2.singular.bold")
let messagePart3 = isPlural ? localized("contacts_received.popup.message.part.3.plural") : localized("contacts_received.popup.message.part.3.singular")
let confirmButtonTitle = isPlural ? localized("contacts_received.popup.buttons.confirm.plural") : localized("contacts_received.popup.buttons.confirm.singular")

return await withCheckedContinuation { continuation in

let model = PopUpDialogModel(
titleComponents: [
StylizedLabel.StylizedText(text: title, style: .normal)
],
messageComponents: [
StylizedLabel.StylizedText(text: localized("contacts_received.popup.message.part.1"), style: .normal),
StylizedLabel.StylizedText(text: messagePart2, style: .bold),
StylizedLabel.StylizedText(text: messagePart3, style: .normal)
],
buttons: [
PopUpDialogButtonModel(title: confirmButtonTitle, type: .normal, callback: { continuation.resume(returning: true) }),
PopUpDialogButtonModel(title: localized("contacts_received.popup.buttons.reject"), type: .text, callback: { continuation.resume(returning: false) })
],
hapticType: .success
)

DispatchQueue.main.async {
PopUpPresenter.showPopUp(model: model)
}
}
}

private static func addContacts(deeplink: ContactListDeeplink) throws {

let contactsManager = ContactsManager()

try deeplink.list
.forEach {
let address = try TariAddress(hex: $0.hex)

if Tari.shared.isWalletConnected {
_ = try contactsManager.createInternalModel(name: $0.alias, isFavorite: false, address: address)
} else {
try PendingDataManager.shared.storeContact(name: $0.alias, isFavorite: false, address: address)
}
}
}
}
51 changes: 3 additions & 48 deletions MobileWallet/Common/Deep Links/DeeplinkHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ enum DeeplinkError: Error {
enum DeeplinkHandler {

static func handle(rawDeeplink: String, handler: DeeplinkHandlable? = nil) throws {
guard let decodedDeeplink = rawDeeplink.removingPercentEncoding, let deeplink = URL(string: decodedDeeplink) else { throw DeeplinkError.unknownDeeplink }
guard let deeplink = URL(string: rawDeeplink) else { throw DeeplinkError.unknownDeeplink }
return try handle(deeplink: deeplink, handler: handler)
}

Expand Down Expand Up @@ -138,8 +138,8 @@ enum DeeplinkHandler {

DispatchQueue.main.async {
guard UIApplication.shared.applicationState == .background else {
showAddContactsDialog(deeplink: contactListDeeplink) {
try? addContacts(deeplink: contactListDeeplink)
Task {
try? await DeepLinkDefaultActionsHandler.handleInForeground(contactListDeeplink: contactListDeeplink)
}
return
}
Expand All @@ -153,51 +153,6 @@ enum DeeplinkHandler {
}
}

private static func showAddContactsDialog(deeplink: ContactListDeeplink, onConfrim: @escaping () -> Void) {

let contactCount = deeplink.list.count
let isPlural = contactCount > 1

let title = isPlural ? localized("contacts_received.popup.title.plural") : localized("contacts_received.popup.title.singular")
let messagePart2 = isPlural ? localized("contacts_received.popup.message.part.2.plural.bold", arguments: contactCount) : localized("contacts_received.popup.message.part.2.singular.bold")
let messagePart3 = isPlural ? localized("contacts_received.popup.message.part.3.plural") : localized("contacts_received.popup.message.part.3.singular")
let confirmButtonTitle = isPlural ? localized("contacts_received.popup.buttons.confirm.plural") : localized("contacts_received.popup.buttons.confirm.singular")

let model = PopUpDialogModel(
titleComponents: [
StylizedLabel.StylizedText(text: title, style: .normal)
],
messageComponents: [
StylizedLabel.StylizedText(text: localized("contacts_received.popup.message.part.1"), style: .normal),
StylizedLabel.StylizedText(text: messagePart2, style: .bold),
StylizedLabel.StylizedText(text: messagePart3, style: .normal)
],
buttons: [
PopUpDialogButtonModel(title: confirmButtonTitle, type: .normal, callback: onConfrim),
PopUpDialogButtonModel(title: localized("contacts_received.popup.buttons.reject"), type: .text)
],
hapticType: .success
)

PopUpPresenter.showPopUp(model: model)
}

private static func addContacts(deeplink: ContactListDeeplink) throws {

let contactsManager = ContactsManager()

try deeplink.list
.forEach {
let address = try TariAddress(hex: $0.hex)

if Tari.shared.isWalletConnected {
_ = try contactsManager.createInternalModel(name: $0.alias, isFavorite: false, address: address)
} else {
try PendingDataManager.shared.storeContact(name: $0.alias, isFavorite: false, address: address)
}
}
}

private static func showCustomDeeplinkPopUp(name: String, peer: String) {

let headerSection = PopUpHeaderWithSubtitle()
Expand Down
14 changes: 11 additions & 3 deletions MobileWallet/Common/Extensions/URL+Tools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ extension URL {
.split(separator: "&")
.map { $0.split(separator: "=") }
.reduce(into: [[String]: String]()) { result, element in
let keys = String(element[0]).splitElementsInBrackets()
let value = String(element[1])
result[keys] = value
switch element.count {
case 1:
let keys = String(element[0]).splitElementsInBrackets()
result[keys] = ""
case 2:
let keys = String(element[0]).splitElementsInBrackets()
let value = String(element[1])
result[keys] = value
default:
break
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ final class BLEPeripheralManager: NSObject {

private func handle(writeRequest: CBATTRequest) {

guard let data = writeRequest.value, let rawDeeplink = String(data: data, encoding: .utf16) else {
guard let data = writeRequest.value, let rawDeeplink = String(data: data, encoding: .utf8) else {
manager.respond(to: writeRequest, withResult: .invalidHandle)
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import Combine
final class AddContactModel {

enum Action {
case endFlow(model: ContactsManager.Model)
case showDetails(model: ContactsManager.Model)
case popBack
}

private enum DataValidationError: Int, Error, Comparable {
Expand Down Expand Up @@ -115,7 +116,7 @@ final class AddContactModel {
do {
guard let address else { return }
let model = try contactsManager.createInternalModel(name: contactName, isFavorite: false, address: address)
action = .endFlow(model: model)
action = .showDetails(model: model)
} catch {
errorMessage = ErrorMessageManager.errorModel(forError: error)
}
Expand All @@ -128,6 +129,17 @@ final class AddContactModel {
searchTextSubject.send(emojis)
}

func handle(deeplink: ContactListDeeplink) {
Task {
do {
guard try await DeepLinkDefaultActionsHandler.handleInForeground(contactListDeeplink: deeplink) else { return }
action = .popBack
} catch {
errorMessage = ErrorMessageManager.errorModel(forError: error)
}
}
}

// MARK: - Handlers

private func handle(searchText: String, isSearchTextFormatted: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ final class AddContactViewController: UIViewController {

model.$action
.compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.handle(action: $0) }
.store(in: &cancellables)

Expand Down Expand Up @@ -124,8 +125,10 @@ final class AddContactViewController: UIViewController {

private func handle(action: AddContactModel.Action) {
switch action {
case let .endFlow(model):
case let .showDetails(model):
navigateToNextScreen(model: model)
case .popBack:
navigationController?.popViewController(animated: true)
}
}

Expand Down Expand Up @@ -165,6 +168,6 @@ extension AddContactViewController: ScanViewControllerDelegate {
}

func onScan(deeplink: ContactListDeeplink) {
navigationController?.popViewController(animated: true)
model.handle(deeplink: deeplink)
}
}
31 changes: 28 additions & 3 deletions MobileWallet/Screens/Contact Book/List/ContactBookModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,30 @@ final class ContactBookModel {

// MARK: - View Model

func handle(transactionSendDeeplink: TransactionsSendDeeplink) {

var amount: MicroTari?

if let rawAmount = transactionSendDeeplink.amount {
amount = MicroTari(rawAmount)
}

let paymentInfo = PaymentInfo(address: transactionSendDeeplink.receiverAddress, yatID: nil, amount: amount, feePerGram: nil, note: transactionSendDeeplink.note)

AppRouter.presentSendTransaction(paymentInfo: paymentInfo)
}

func handle(contactListDeeplink: ContactListDeeplink) {
Task {
do {
guard try await DeepLinkDefaultActionsHandler.handleInForeground(contactListDeeplink: contactListDeeplink) else { return }
fetchContacts()
} catch {
errorModel = ErrorMessageManager.errorModel(forError: error)
}
}
}

func fetchContacts() {
Task {
do {
Expand Down Expand Up @@ -312,7 +336,7 @@ final class ContactBookModel {

private func shareLinkViaBLE(deeplink: URL) {

guard let payload = deeplink.absoluteString.data(using: .utf16) else { return }
guard let payload = deeplink.absoluteString.data(using: .utf8) else { return }

action = .showBLEWaitingForReceiverDialog

Expand Down Expand Up @@ -445,9 +469,10 @@ final class ContactBookModel {
let section = SectionType(rawValue: data.offset)

let viewModels = data.element.map {
ContactViewModel(
let name = (!$0.name.isEmpty ? $0.name : $0.internalModel?.emojiID.obfuscatedText) ?? ""
return ContactViewModel(
id: $0.id,
name: $0.name,
name: name,
avatar: $0.avatar,
avatarImage: $0.avatarImage,
isFavorite: $0.isFavorite,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,12 @@ final class ContactBookViewController: UIViewController {

extension ContactBookViewController: ScanViewControllerDelegate {

func onScan(deeplink: TransactionsSendDeeplink) {
model.handle(transactionSendDeeplink: deeplink)
}

func onScan(deeplink: ContactListDeeplink) {
model.fetchContacts()
model.handle(contactListDeeplink: deeplink)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ final class ExternalContactsManager {

do {
try store.enumerateContacts(with: request) { contact, _ in

guard !contact.givenName.isEmpty || !contact.familyName.isEmpty else { return }

let emojiID = contact.socialProfiles.first { $0.value.service == Self.auroraServiceName }?.value.username
let yat = contact.socialProfiles.first { $0.value.service == Self.yatServiceName }?.value.username
var avatar: UIImage?
Expand Down
Loading

0 comments on commit c89dc7b

Please sign in to comment.