From f641b0201d1eecb6f3888b84d86f3e5947070214 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 6 Dec 2024 17:52:13 +0200 Subject: [PATCH] feat: initialize push notifications; add pull to refresh --- .../ios/App/App.xcodeproj/project.pbxproj | 22 +++- apps/tablet/ios/App/App/App.entitlements | 2 +- apps/tablet/ios/App/App/AppDelegate.swift | 18 +++ .../ios/App/App/AppRelease.entitlements | 12 ++ .../ios/App/App/GoogleService-Info.plist | 30 +++++ apps/tablet/ios/App/App/Info.plist | 61 +++++----- apps/tablet/ios/App/Podfile | 4 + apps/tablet/ios/App/Podfile.lock | 115 +++++++++++++++--- apps/tablet/package.json | 1 + apps/tablet/src/app/App.tsx | 53 +++++++- .../src/app/components/PullToRefresh.tsx | 111 +++++++++++++++++ yarn.lock | 10 ++ 12 files changed, 382 insertions(+), 57 deletions(-) create mode 100644 apps/tablet/ios/App/App/AppRelease.entitlements create mode 100644 apps/tablet/ios/App/App/GoogleService-Info.plist create mode 100644 apps/tablet/src/app/components/PullToRefresh.tsx diff --git a/apps/tablet/ios/App/App.xcodeproj/project.pbxproj b/apps/tablet/ios/App/App.xcodeproj/project.pbxproj index 1595e0911..9878fc21c 100644 --- a/apps/tablet/ios/App/App.xcodeproj/project.pbxproj +++ b/apps/tablet/ios/App/App.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ B3313CCD2CEB43AB00E31C31 /* CustomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3313CCC2CEB43AB00E31C31 /* CustomViewController.swift */; }; B3313CCF2CEB459400E31C31 /* BiometricPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3313CCE2CEB459400E31C31 /* BiometricPlugin.swift */; }; B3313CD12CEB4C7E00E31C31 /* SecureStoragePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3313CD02CEB4C7E00E31C31 /* SecureStoragePlugin.swift */; }; + B3E0A8272D03497E0089241A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B3E0A8262D03497E0089241A /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -36,6 +37,8 @@ B3313CCE2CEB459400E31C31 /* BiometricPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricPlugin.swift; sourceTree = ""; }; B3313CD02CEB4C7E00E31C31 /* SecureStoragePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureStoragePlugin.swift; sourceTree = ""; }; B3CCE8EB2CFDE9B40083D875 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; + B3E0A8262D03497E0089241A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + B3E0A8282D034A140089241A /* AppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppRelease.entitlements; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -80,6 +83,8 @@ 504EC3061FED79650016851F /* App */ = { isa = PBXGroup; children = ( + B3E0A8282D034A140089241A /* AppRelease.entitlements */, + B3E0A8262D03497E0089241A /* GoogleService-Info.plist */, B3CCE8EB2CFDE9B40083D875 /* App.entitlements */, 50379B222058CBB4000EE86E /* capacitor.config.json */, 504EC3071FED79650016851F /* AppDelegate.swift */, @@ -174,6 +179,7 @@ 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, 504EC30D1FED79650016851F /* Main.storyboard in Resources */, 2FAD9763203C412B000D30F8 /* config.xml in Resources */, + B3E0A8272D03497E0089241A /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -364,7 +370,7 @@ CODE_SIGN_ENTITLEMENTS = App/App.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S22MQ9763W; + DEVELOPMENT_TEAM = CT523DK2KC; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -372,9 +378,12 @@ OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.tonapps.tonkeeperpro; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 2; }; name = Debug; }; @@ -383,19 +392,22 @@ baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = App/App.entitlements; + CODE_SIGN_ENTITLEMENTS = App/AppRelease.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S22MQ9763W; + DEVELOPMENT_TEAM = CT523DK2KC; INFOPLIST_FILE = App/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.tonapps.tonkeeperpro; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 2; }; name = Release; }; diff --git a/apps/tablet/ios/App/App/App.entitlements b/apps/tablet/ios/App/App/App.entitlements index 1a7f7ac44..06cfa3365 100644 --- a/apps/tablet/ios/App/App/App.entitlements +++ b/apps/tablet/ios/App/App/App.entitlements @@ -4,7 +4,7 @@ keychain-access-groups - $(AppIdentifierPrefix)com.tonkeeper.pro.app + $(AppIdentifierPrefix)com.tonapps.tonkeeperpro diff --git a/apps/tablet/ios/App/App/AppDelegate.swift b/apps/tablet/ios/App/App/AppDelegate.swift index c3cd83b5c..0f2fd9487 100644 --- a/apps/tablet/ios/App/App/AppDelegate.swift +++ b/apps/tablet/ios/App/App/AppDelegate.swift @@ -1,5 +1,7 @@ import UIKit import Capacitor +import FirebaseCore +import FirebaseMessaging @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -8,6 +10,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + FirebaseApp.configure() + return true } @@ -45,5 +49,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + Messaging.messaging().token(completion: { (token, error) in + if let error = error { + NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) + } else if let token = token { + NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: token) + } + }) + } + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) + } } diff --git a/apps/tablet/ios/App/App/AppRelease.entitlements b/apps/tablet/ios/App/App/AppRelease.entitlements new file mode 100644 index 000000000..bb30651f7 --- /dev/null +++ b/apps/tablet/ios/App/App/AppRelease.entitlements @@ -0,0 +1,12 @@ + + + + + aps-environment + development + keychain-access-groups + + $(AppIdentifierPrefix)com.tonapps.tonkeeperpro + + + diff --git a/apps/tablet/ios/App/App/GoogleService-Info.plist b/apps/tablet/ios/App/App/GoogleService-Info.plist new file mode 100644 index 000000000..d03fe032c --- /dev/null +++ b/apps/tablet/ios/App/App/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyBfuV_ymekwDZwlD6TkvKB5292oYd96dbM + GCM_SENDER_ID + 488327313434 + PLIST_VERSION + 1 + BUNDLE_ID + com.tonapps.tonkeeperpro + PROJECT_ID + tonkeeper-web + STORAGE_BUCKET + tonkeeper-web.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:488327313434:ios:85fdb7916fec750461f9a3 + + \ No newline at end of file diff --git a/apps/tablet/ios/App/App/Info.plist b/apps/tablet/ios/App/App/Info.plist index 35915c8e2..46b89b4fc 100644 --- a/apps/tablet/ios/App/App/Info.plist +++ b/apps/tablet/ios/App/App/Info.plist @@ -18,10 +18,31 @@ APPL CFBundleShortVersionString $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleURLSchemes + + tonkeeper + tonkeeper-tc + tc + ton + + + CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSCameraUsageDescription + We need access to your camera to read QR codes. + NSFaceIDUsageDescription + We use Face ID to securely authenticate your access to mnemonic key. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -30,48 +51,26 @@ armv7 + UIStatusBarHidden + UISupportedInterfaceOrientations - UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown UIViewControllerBasedStatusBarAppearance - UIStatusBarHidden - - WKAppBoundDomains - - dev-pro.tonconsole.com - pro.tonconsole.com - - NSFaceIDUsageDescription - We use Face ID to securely authenticate your access to mnemonic key. - CFBundleURLTypes - - - CFBundleURLSchemes - - tonkeeper - tonkeeper-tc - tc - ton - - - - NSCameraUsageDescription - We need access to your camera to read QR codes. - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - + WKAppBoundDomains + + dev-pro.tonconsole.com + pro.tonconsole.com + diff --git a/apps/tablet/ios/App/Podfile b/apps/tablet/ios/App/Podfile index c7ac4e350..291234d5a 100644 --- a/apps/tablet/ios/App/Podfile +++ b/apps/tablet/ios/App/Podfile @@ -14,13 +14,17 @@ def capacitor_pods pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app' pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera' pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard' + pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device' pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences' + pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications' pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' end target 'App' do capacitor_pods # Add your Pods here + + pod 'FirebaseMessaging' end post_install do |installer| diff --git a/apps/tablet/ios/App/Podfile.lock b/apps/tablet/ios/App/Podfile.lock index 5b5e28ac8..9374e3041 100644 --- a/apps/tablet/ios/App/Podfile.lock +++ b/apps/tablet/ios/App/Podfile.lock @@ -1,17 +1,74 @@ PODS: - - Capacitor (6.1.2): + - Capacitor (6.2.0): - CapacitorCordova - - CapacitorApp (6.0.1): + - CapacitorApp (6.0.2): - Capacitor - - CapacitorCamera (6.1.0): + - CapacitorCamera (6.1.1): - Capacitor - - CapacitorClipboard (6.0.1): + - CapacitorClipboard (6.0.2): - Capacitor - - CapacitorCordova (6.1.2) - - CapacitorPreferences (6.0.2): + - CapacitorCordova (6.2.0) + - CapacitorDevice (6.0.2): - Capacitor - - CapacitorSplashScreen (6.0.2): + - CapacitorPreferences (6.0.3): - Capacitor + - CapacitorPushNotifications (6.0.3): + - Capacitor + - CapacitorSplashScreen (6.0.3): + - Capacitor + - FirebaseCore (11.6.0): + - FirebaseCoreInternal (~> 11.6.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreInternal (11.6.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FirebaseInstallations (11.6.0): + - FirebaseCore (~> 11.6.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - FirebaseMessaging (11.6.0): + - FirebaseCore (~> 11.6.0) + - FirebaseInstallations (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Reachability (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - nanopb (~> 3.30910.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleUtilities/AppDelegateSwizzler (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.0.2): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.0.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.0.2)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/Reachability (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - PromisesObjC (2.4.0) DEPENDENCIES: - "Capacitor (from `../../node_modules/@capacitor/ios`)" @@ -19,8 +76,22 @@ DEPENDENCIES: - "CapacitorCamera (from `../../node_modules/@capacitor/camera`)" - "CapacitorClipboard (from `../../node_modules/@capacitor/clipboard`)" - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" + - "CapacitorDevice (from `../../node_modules/@capacitor/device`)" - "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)" + - "CapacitorPushNotifications (from `../../node_modules/@capacitor/push-notifications`)" - "CapacitorSplashScreen (from `../../node_modules/@capacitor/splash-screen`)" + - FirebaseMessaging + +SPEC REPOS: + trunk: + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - FirebaseMessaging + - GoogleDataTransport + - GoogleUtilities + - nanopb + - PromisesObjC EXTERNAL SOURCES: Capacitor: @@ -33,20 +104,34 @@ EXTERNAL SOURCES: :path: "../../node_modules/@capacitor/clipboard" CapacitorCordova: :path: "../../node_modules/@capacitor/ios" + CapacitorDevice: + :path: "../../node_modules/@capacitor/device" CapacitorPreferences: :path: "../../node_modules/@capacitor/preferences" + CapacitorPushNotifications: + :path: "../../node_modules/@capacitor/push-notifications" CapacitorSplashScreen: :path: "../../node_modules/@capacitor/splash-screen" SPEC CHECKSUMS: - Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2 - CapacitorApp: 0bc633b4eae40a1f32cd2834788fad3bc42da6a1 - CapacitorCamera: 81ce64062cd82b82cc75a79616fda4bd09d7f643 - CapacitorClipboard: 756cd7e83e8d5d19b0c74f40b57517c287bd5fe2 - CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd - CapacitorPreferences: e8284bf740cf8c6d3f25409af3c01df87dfeb5a1 - CapacitorSplashScreen: 250df9ef8014fac5c7c1fd231f0f8b1d8f0b5624 + Capacitor: 05d35014f4425b0740fc8776481f6a369ad071bf + CapacitorApp: e1e6b7d05e444d593ca16fd6d76f2b7c48b5aea7 + CapacitorCamera: fc099c42b3cbb1e8e945bc945114ff830fa82dfd + CapacitorClipboard: 4443c3cdb7c77b1533dfe3ff0f9f7756aa8579df + CapacitorCordova: b33e7f4aa4ed105dd43283acdd940964374a87d9 + CapacitorDevice: 9efd479d71d1baad74b75df531184c3f730eaa48 + CapacitorPreferences: f3eadae2369ac3ab8e21743a2959145b0d1286a3 + CapacitorPushNotifications: 9b178e010634d2f7bfca97b81478503463f86b6c + CapacitorSplashScreen: fd8bf1bf9081d9aa8817b7cd37d740d1bdaf2fb2 + FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa + FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2 + FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c + FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 -PODFILE CHECKSUM: db26d8f446e81926aade6104148f6510bafadf17 +PODFILE CHECKSUM: f9d707744c97cd8f502d129ac50e6f2a2abd9aca COCOAPODS: 1.16.2 diff --git a/apps/tablet/package.json b/apps/tablet/package.json index b1bb314d5..bcac63630 100644 --- a/apps/tablet/package.json +++ b/apps/tablet/package.json @@ -28,6 +28,7 @@ "@capacitor/device": "^6.0.2", "@capacitor/ios": "^6.1.2", "@capacitor/preferences": "^6.0.2", + "@capacitor/push-notifications": "^6.0.3", "@capacitor/splash-screen": "latest", "@tanstack/react-query": "4.3.4", "@tonkeeper/core": "0.1.0", diff --git a/apps/tablet/src/app/App.tsx b/apps/tablet/src/app/App.tsx index 1dd82a14d..5a5f10253 100644 --- a/apps/tablet/src/app/App.tsx +++ b/apps/tablet/src/app/App.tsx @@ -72,7 +72,7 @@ import { useActiveTonNetwork } from '@tonkeeper/uikit/dist/state/wallet'; import { Container, GlobalStyleCss } from '@tonkeeper/uikit/dist/styles/globalStyle'; -import { FC, Suspense, useEffect, useMemo } from 'react'; +import { FC, Suspense, useCallback, useEffect, useMemo } from "react"; import { useTranslation } from 'react-i18next'; import { Outlet, @@ -91,6 +91,8 @@ import { useGlobalPreferencesQuery } from '@tonkeeper/uikit/dist/state/global-pr import { DesktopManageMultisigsPage } from '@tonkeeper/uikit/dist/desktop-pages/manage-multisig-wallets/DesktopManageMultisigs'; import { useGlobalSetup } from '@tonkeeper/uikit/dist/state/globalSetup'; import { DesktopMultisigOrdersPage } from '@tonkeeper/uikit/dist/desktop-pages/multisig-orders/DesktopMultisigOrders'; +import { ActionPerformed, PushNotifications, PushNotificationSchema, Token } from '@capacitor/push-notifications'; +import { PullToRefresh } from "./components/PullToRefresh"; const queryClient = new QueryClient({ defaultOptions: { @@ -156,6 +158,43 @@ export const Providers = () => { document.body.classList.add(TABLET_APPLICATION_ID); }, []); + /** + * TODO remove + */ + useEffect(() => { + PushNotifications.requestPermissions().then((result) => { + if (result.receive === 'granted') { + // Register with Apple / Google to receive push via APNS/FCM + PushNotifications.register(); + } else { + // Show some error + } + }); + + // On success, we should be able to receive notifications + PushNotifications.addListener('registration', (token: Token) => { + alert('Push registration success, token: ' + token.value); + }); + + // Some issue with our setup and push will not work + PushNotifications.addListener('registrationError', (error: any) => { + alert('Error on registration: ' + JSON.stringify(error)); + }); + + // Show us the notification payload if the app is open on our device + PushNotifications.addListener('pushNotificationReceived', (notification: PushNotificationSchema) => { + alert('Push received: ' + JSON.stringify(notification)); + }); + + // Method called when tapping on a notification + PushNotifications.addListener('pushNotificationActionPerformed', (notification: ActionPerformed) => { + alert('Push action performed: ' + JSON.stringify(notification)); + }); + }, []); + /** + * TODO remove + */ + return ( }> @@ -296,10 +335,6 @@ export const Loader: FC = () => { } }, [lang, i18n]); - useEffect(() => { - // window.backgroundApi.onRefresh(() => queryClient.invalidateQueries()); TODO - }, []); - if ( activeWalletLoading || isLangLoading || @@ -469,6 +504,13 @@ const OldAppRouting = () => { }; const BackgroundElements = () => { + const onRefresh = useCallback(async () => { + const promise1 = queryClient.invalidateQueries(); + const promise2 = new Promise(r => setTimeout(r, 1000)); + + await Promise.all([promise1, promise2]); + }, []); + return ( <> @@ -482,6 +524,7 @@ const BackgroundElements = () => { + ); }; diff --git a/apps/tablet/src/app/components/PullToRefresh.tsx b/apps/tablet/src/app/components/PullToRefresh.tsx new file mode 100644 index 000000000..8cdd9d287 --- /dev/null +++ b/apps/tablet/src/app/components/PullToRefresh.tsx @@ -0,0 +1,111 @@ +import styled from "styled-components"; +import { useState, useCallback, useEffect, FC } from "react"; +import { RefreshIcon } from "@tonkeeper/uikit/dist/components/Icon"; + +export const THRESHOLD = 64; + +export function usePullToRefresh(onRefresh:() => Promise ) { + const [startY, setStartY] = useState(0); + const [pullProgress, setPullProgress] = useState(0); + const [isRefreshing, setIsRefreshing] = useState(false); + + const handleTouchStart = useCallback((e: TouchEvent) => { + if (window.scrollY === 0) { + setStartY(e.touches[0].clientY); + } + }, []); + + const handleTouchMove = useCallback((e: TouchEvent) => { + if (startY === 0 || isRefreshing) return; + + const y = e.touches[0].clientY; + const pull = Math.max(0, Math.min(y - startY, THRESHOLD)); + setPullProgress(pull / THRESHOLD); + + // Prevent default scrolling while pulling + if (pull > 0) { + e.preventDefault(); + } + }, [startY, isRefreshing]); + + const handleTouchEnd = useCallback(async () => { + if (pullProgress >= 1 && !isRefreshing) { + setIsRefreshing(true); + try { + await onRefresh(); + } finally { + setIsRefreshing(false); + } + } + setStartY(0); + setPullProgress(0); + }, [pullProgress, isRefreshing, onRefresh]); + + useEffect(() => { + document.addEventListener('touchstart', handleTouchStart, { passive: false }); + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleTouchEnd); + + return () => { + document.removeEventListener('touchstart', handleTouchStart); + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleTouchEnd); + }; + }, [handleTouchStart, handleTouchMove, handleTouchEnd]); + + return { + pullProgress, + isRefreshing, + }; +} + + +export const RefreshContainer = styled.div<{ $pullProgress: number }>` + position: fixed; + top: 0; + left: 50%; + transform: translateX(-50%) translateY(${props => props.$pullProgress > 0 ? '16px' : '-100%'}); + height: 36px; + width: 36px; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.2s; + z-index: 50; + background: ${p => p.theme.buttonTertiaryBackground}; + border-radius: 50%; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.04); +`; + +export const IconWrapper = styled.div<{ $rotation: number; $isRefreshing: boolean }>` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + transform: rotate(${props => props.$rotation}deg); + transition: transform 0.2s; + animation: ${props => props.$isRefreshing ? 'spin 1s linear infinite' : 'none'}; + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } +`; + +export const PullToRefresh: FC<{ onRefresh: () => Promise}> = ({ onRefresh }) => { + const { pullProgress, isRefreshing } = usePullToRefresh(onRefresh); + const rotation = Math.min(pullProgress * 360, 360); + + return ( + + + + + + ); +} diff --git a/yarn.lock b/yarn.lock index 2f3a2048e..2f7260a33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2358,6 +2358,15 @@ __metadata: languageName: node linkType: hard +"@capacitor/push-notifications@npm:^6.0.3": + version: 6.0.3 + resolution: "@capacitor/push-notifications@npm:6.0.3" + peerDependencies: + "@capacitor/core": ^6.0.0 + checksum: 10/86ada486405b966dfb4b8fc8c11527fe12376007a437c642f6a19dd6592e1bf1f960ae4d9f83f711a6206fdd2448efe58536fa8efb3646152b9b201f52aaf566 + languageName: node + linkType: hard + "@capacitor/splash-screen@npm:latest": version: 6.0.3 resolution: "@capacitor/splash-screen@npm:6.0.3" @@ -7514,6 +7523,7 @@ __metadata: "@capacitor/device": "npm:^6.0.2" "@capacitor/ios": "npm:^6.1.2" "@capacitor/preferences": "npm:^6.0.2" + "@capacitor/push-notifications": "npm:^6.0.3" "@capacitor/splash-screen": "npm:latest" "@tanstack/react-query": "npm:4.3.4" "@tonkeeper/core": "npm:0.1.0"