Merge pull request #723 from alpha-wallet/show-local-notifications-when-eth-known-tokens-received

Show local notification to indicate user has received ether. Only while app is active
pull/725/head
James Sangalli 6 years ago committed by GitHub
commit d459f4019b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      AlphaWallet.xcodeproj/project.pbxproj
  2. 10
      AlphaWallet/AppCoordinator.swift
  3. 5
      AlphaWallet/InCoordinator.swift
  4. 2
      AlphaWallet/Localization/en.lproj/Localizable.strings
  5. 2
      AlphaWallet/Localization/es.lproj/Localizable.strings
  6. 2
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  7. 65
      AlphaWallet/PushNotificationsCoordinator.swift
  8. 2
      AlphaWallet/Settings/Types/Constants.swift
  9. 3
      AlphaWallet/Transactions/Coordinators/TransactionCoordinator.swift
  10. 34
      AlphaWallet/Transactions/Coordinators/TransactionDataCoordinator.swift
  11. 4
      AlphaWalletTests/Coordinators/AppCoordinatorTests.swift

@ -394,6 +394,7 @@
5E7C7CCA357CB7BF12E1F2B4 /* UIStackView+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73ED9226646D562B5A3C /* UIStackView+Array.swift */; };
5E7C7CCC8D376C6E5C245715 /* EthCurrencyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C73DF5FBFE756097D32B1 /* EthCurrencyHelper.swift */; };
5E7C7CDB837DCD57E0594CBA /* TokensCardViewControllerTitleHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7821694C489D5114DB18 /* TokensCardViewControllerTitleHeader.swift */; };
5E7C7CDE2814481CD7BC47AC /* PushNotificationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7FC75FF544B1DF0B0D8B /* PushNotificationsCoordinator.swift */; };
5E7C7CE5CA19183FCED8C907 /* TokensViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C7EE467A7F5F2E5B1F660 /* TokensViewModel.swift */; };
5E7C7CF06533EDACC8E220B3 /* StaticHTMLViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C764B98F526271E4C2A6A /* StaticHTMLViewController.swift */; };
5E7C7CF3BB38045FA40F38AE /* PrivacyPolicyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7C72142D5817EF8FA8CADA /* PrivacyPolicyViewController.swift */; };
@ -1003,6 +1004,7 @@
5E7C7F932B48011A24C26733 /* TokensCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokensCoordinator.swift; sourceTree = "<group>"; };
5E7C7FB99843529061368DA1 /* LocalesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalesViewModel.swift; sourceTree = "<group>"; };
5E7C7FC30FF22C3EA71451BC /* EthTypedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EthTypedData.swift; sourceTree = "<group>"; };
5E7C7FC75FF544B1DF0B0D8B /* PushNotificationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationsCoordinator.swift; sourceTree = "<group>"; };
5E7C7FCE2427A30ACD860DF8 /* ServerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerViewModel.swift; sourceTree = "<group>"; };
5E7C7FE30D58E4022AF04E48 /* AssetDefinitionStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetDefinitionStoreTests.swift; sourceTree = "<group>"; };
5E7C7FF84A4377FC395772C3 /* SellTokensCardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SellTokensCardViewController.swift; sourceTree = "<group>"; };
@ -1246,6 +1248,7 @@
5E7C71698FE1429F1AC0777D /* Sell */,
5E7C7EFD61F536B335D5FD3F /* AssetDefinition */,
5E7C7CE9D3BEED5AA1FC1599 /* RPC */,
5E7C7FC75FF544B1DF0B0D8B /* PushNotificationsCoordinator.swift */,
);
path = AlphaWallet;
sourceTree = "<group>";
@ -3863,6 +3866,7 @@
5E7C759BD6B4DDC39A85874B /* Web3RequestType.swift in Sources */,
5E7C7656941A4EDAF121D3CB /* Web3Request.swift in Sources */,
5E7C7A3FEE2826C05AB42656 /* Web3Swift.swift in Sources */,
5E7C7CDE2814481CD7BC47AC /* PushNotificationsCoordinator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

@ -22,6 +22,9 @@ class AppCoordinator: NSObject, Coordinator {
var inCoordinator: InCoordinator? {
return coordinators.first { $0 is InCoordinator } as? InCoordinator
}
var pushNotificationsCoordinator: PushNotificationsCoordinator? {
return coordinators.first { $0 is PushNotificationsCoordinator } as? PushNotificationsCoordinator
}
private var universalLinkCoordinator: UniversalLinkCoordinator? {
return coordinators.first { $0 is UniversalLinkCoordinator } as? UniversalLinkCoordinator
}
@ -107,6 +110,9 @@ class AppCoordinator: NSObject, Coordinator {
func handleNotifications() {
UIApplication.shared.applicationIconBadgeNumber = 0
let coordinator = PushNotificationsCoordinator()
coordinator.start()
addCoordinator(coordinator)
}
func resetToWelcomeScreen() {
@ -215,6 +221,10 @@ extension AppCoordinator: InCoordinatorDelegate {
func didUpdateAccounts(in coordinator: InCoordinator) {
}
func didShowWallet(in coordinator: InCoordinator) {
pushNotificationsCoordinator?.didShowWallet(in: coordinator.navigationController)
}
}
extension AppCoordinator: UniversalLinkCoordinatorDelegate {

@ -9,6 +9,7 @@ import BigInt
protocol InCoordinatorDelegate: class {
func didCancel(in coordinator: InCoordinator)
func didUpdateAccounts(in coordinator: InCoordinator)
func didShowWallet(in coordinator: InCoordinator)
}
enum Tabs {
@ -49,6 +50,9 @@ class InCoordinator: Coordinator {
$0 as? TransactionCoordinator
}.first
}
private var transactionCoordinators: [TransactionCoordinator] {
return coordinators.compactMap { $0 as? TransactionCoordinator }
}
var tabBarController: UITabBarController? {
return navigationController.viewControllers.first as? UITabBarController
@ -540,6 +544,7 @@ extension InCoordinator: SettingsCoordinatorDelegate {
func didPressShowWallet(in coordinator: SettingsCoordinator) {
showPaymentFlow(for: .request)
delegate?.didShowWallet(in: self)
}
}

@ -77,6 +77,8 @@
"wallet.import.button.title" = "Import Wallet";
"wallets.backup.alertSheet.title" = "Backup Encrypted Private Key";
"transactions.tabbar.item.title" = "My Transactions";
"transactions.received.ether" = "You have received %@ ether";
"transactions.received.ether.notification.prompt" = "Allow Notifications When You Receive Ether?";
"transaction.navigation.title" = "Transaction";
"import.navigation.title" = "Import Wallet";
"configureTransaction.data.label.title" = "Transaction Data (Optional)";

@ -84,6 +84,8 @@
"wallet.import.button.title" = "Import Wallet";
"wallets.backup.alertSheet.title" = "Backup Encrypted Private Key";
"transactions.tabbar.item.title" = "Actas";
"transactions.received.ether" = "You have received %@ ether";
"transactions.received.ether.notification.prompt" = "Allow Notifications When You Receive Ether?";
"import.navigation.title" = "Importando cartera";
"browser.home.button.title" = "Home";
"browser.reload.button.title" = "Reload";

@ -66,6 +66,8 @@
"wallet.navigation.title" = "钱包";
"wallets.backup.alertSheet.title" = "备份加密后的私钥文档";
"transactions.tabbar.item.title" = "我的账单";
"transactions.received.ether" = "You have received %@ ether";
"transactions.received.ether.notification.prompt" = "Allow Notifications When You Receive Ether?";
"transaction.navigation.title" = "交易";
"import.navigation.title" = "导入钱包";
"configureTransaction.data.label.title" = "交易数据(可选)";

@ -0,0 +1,65 @@
// Copyright © 2018 Stormbird PTE. LTD.
import Foundation
import UIKit
import UserNotifications
class PushNotificationsCoordinator: NSObject, Coordinator {
var coordinators: [Coordinator] = []
private var notificationCenter: UNUserNotificationCenter {
return .current()
}
func start() {
notificationCenter.delegate = self
}
func didShowWallet(in navigationController: UINavigationController) {
promptToEnableNotification(in: navigationController)
}
private func promptToEnableNotification(in navigationController: UINavigationController) {
authorizationNotDetermined {
navigationController.visibleViewController?.confirm(
title: R.string.localizable.transactionsReceivedEtherNotificationPrompt(),
message: nil,
okTitle: R.string.localizable.oK(),
okStyle: .default
) { result in
switch result {
case .success:
//Give some time for the view controller to show up first. We don't have to be precise, so no need to complicate things with hooking up to the view controller's animation
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.requestForAuthorization()
}
case .failure:
break
}
}
}
}
private func authorizationNotDetermined(handler: @escaping () -> Void) {
notificationCenter.getNotificationSettings { settings in
if case .notDetermined = settings.authorizationStatus {
handler()
}
}
}
//TODO call this after send Ether too?
private func requestForAuthorization() {
notificationCenter.requestAuthorization(options: [.alert, .sound]) { granted, error in
}
}
}
extension PushNotificationsCoordinator: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert,.sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
completionHandler()
}
}

@ -78,6 +78,8 @@ public struct Constants {
//contract addresses that are compatible with opensea
//See comment in CryptoKittyHandling enum
public static let cryptoKittiesContractAddress = "0x06012c8cf97bead5deae237070f9587f8e7a266d"
public static let etherReceivedNotificationIdentifier = "etherReceivedNotificationIdentifier"
}
public struct UnitConfiguration {

@ -21,7 +21,8 @@ class TransactionCoordinator: Coordinator {
lazy var dataCoordinator: TransactionDataCoordinator = {
let coordinator = TransactionDataCoordinator(
session: session,
storage: storage
storage: storage,
keystore: keystore
)
return coordinator
}()

@ -2,12 +2,14 @@
import Foundation
import UIKit
import BigInt
import JSONRPCKit
import APIKit
import RealmSwift
import Result
import Moya
import TrustKeystore
import UserNotifications
enum TransactionError: Error {
case failedToFetch
@ -26,6 +28,7 @@ class TransactionDataCoordinator {
let storage: TransactionsStorage
let session: WalletSession
private let keystore: Keystore
let config = Config()
var viewModel: TransactionsViewModel {
return .init(transactions: storage.objects)
@ -38,13 +41,16 @@ class TransactionDataCoordinator {
return TransactionsTracker(sessionID: session.sessionID)
}()
private let trustProvider = TrustProviderFactory.makeProvider()
private var previousTransactions: [Transaction]?
init(
session: WalletSession,
storage: TransactionsStorage
storage: TransactionsStorage,
keystore: Keystore
) {
self.session = session
self.storage = storage
self.keystore = keystore
NotificationCenter.default.addObserver(self, selector: #selector(stopTimers), name: .UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(restartTimers), name: .UIApplicationDidBecomeActive, object: nil)
}
@ -192,7 +198,33 @@ class TransactionDataCoordinator {
handleUpdateItems()
}
private func notifyUserEtherReceivedInNewTransactions() {
if let previousTransactions = previousTransactions {
let diff = storage.objects - previousTransactions
if let wallet = keystore.recentlyUsedWallet {
let newIncomingEthTransactions = diff.filter { $0.to.sameContract(as: wallet.address.eip55String) }
let formatter = EtherNumberFormatter.short
for each in newIncomingEthTransactions {
let amount = formatter.string(from: BigInt(each.value) ?? BigInt(), decimals: 18)
notifyUserEtherReceived(for: each.id, amount: amount)
}
}
}
previousTransactions = storage.objects
}
private func notifyUserEtherReceived(for transactionId: String, amount: String) {
let notificationCenter = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.body = R.string.localizable.transactionsReceivedEther(amount)
content.sound = .default()
let identifier = Constants.etherReceivedNotificationIdentifier
let request = UNNotificationRequest(identifier: "\(identifier):\(transactionId)", content: content, trigger: nil)
notificationCenter.add(request)
}
func handleUpdateItems() {
notifyUserEtherReceivedInNewTransactions()
delegate?.didUpdate(result: .success(storage.objects))
}

@ -26,7 +26,7 @@ class AppCoordinatorTests: XCTestCase {
coordinator.start()
XCTAssertEqual(1, coordinator.coordinators.count)
XCTAssertEqual(2, coordinator.coordinators.count)
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is UITabBarController)
}
@ -81,7 +81,7 @@ class AppCoordinatorTests: XCTestCase {
coordinator.showTransactions(for: .make())
XCTAssertEqual(1, coordinator.coordinators.count)
XCTAssertEqual(2, coordinator.coordinators.count)
XCTAssertTrue(coordinator.navigationController.viewControllers[0] is UITabBarController)
}

Loading…
Cancel
Save