Skip to content

Commit 2d687d9

Browse files
authored
chore: on android use notification to display file upload progress instead of toast (#2628) [skip e2e]
1 parent 6a7c527 commit 2d687d9

File tree

12 files changed

+257
-51
lines changed

12 files changed

+257
-51
lines changed
Binary file not shown.

packages/mobile/ios/Podfile.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,11 @@ PODS:
507507
- React-Core
508508
- RNKeychain (8.1.2):
509509
- React-Core
510+
- RNNotifee (7.8.0):
511+
- React-Core
512+
- RNNotifee/NotifeeCore (= 7.8.0)
513+
- RNNotifee/NotifeeCore (7.8.0):
514+
- React-Core
510515
- RNPrivacySnapshot (1.0.0):
511516
- React-Core
512517
- RNShare (9.4.1):
@@ -593,6 +598,7 @@ DEPENDENCIES:
593598
- RNFS (from `../node_modules/react-native-fs`)
594599
- RNIap (from `../node_modules/react-native-iap`)
595600
- RNKeychain (from `../node_modules/react-native-keychain`)
601+
- "RNNotifee (from `../node_modules/@notifee/react-native`)"
596602
- RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
597603
- RNShare (from `../node_modules/react-native-share`)
598604
- RNStoreReview (from `../node_modules/react-native-store-review`)
@@ -716,6 +722,8 @@ EXTERNAL SOURCES:
716722
:path: "../node_modules/react-native-iap"
717723
RNKeychain:
718724
:path: "../node_modules/react-native-keychain"
725+
RNNotifee:
726+
:path: "../node_modules/@notifee/react-native"
719727
RNPrivacySnapshot:
720728
:path: "../node_modules/react-native-privacy-snapshot"
721729
RNShare:
@@ -789,6 +797,7 @@ SPEC CHECKSUMS:
789797
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
790798
RNIap: c397f49db45af3b10dca64b2325f21bb8078ad21
791799
RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c
800+
RNNotifee: f3c01b391dd8e98e67f539f9a35a9cbcd3bae744
792801
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
793802
RNShare: 32e97adc8d8c97d4a26bcdd3c45516882184f8b6
794803
RNStoreReview: 923b1c888c13469925bf0256dc2c046eab557ce5

packages/mobile/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"node": ">=16"
7373
},
7474
"dependencies": {
75+
"@notifee/react-native": "^7.8.0",
7576
"react-native-store-review": "^0.4.1"
7677
}
7778
}

packages/mobile/src/Lib/MobileDevice.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { Database } from './Database/Database'
5050
import { isLegacyIdentifier } from './Database/LegacyIdentifier'
5151
import { LegacyKeyValueStore } from './Database/LegacyKeyValueStore'
5252
import Keychain from './Keychain'
53+
import notifee, { AuthorizationStatus, Notification } from '@notifee/react-native'
5354

5455
export type BiometricsType = 'Fingerprint' | 'Face ID' | 'Biometrics' | 'Touch ID'
5556

@@ -75,7 +76,40 @@ export class MobileDevice implements MobileDeviceInterface {
7576
private stateObserverService?: AppStateObserverService,
7677
private androidBackHandlerService?: AndroidBackHandlerService,
7778
private colorSchemeService?: ColorSchemeObserverService,
78-
) {}
79+
) {
80+
this.initializeNotifications().catch(console.error)
81+
}
82+
83+
async initializeNotifications() {
84+
if (Platform.OS !== 'android') {
85+
return
86+
}
87+
88+
await notifee.createChannel({
89+
id: 'files',
90+
name: 'File Upload/Download',
91+
})
92+
}
93+
94+
async canDisplayNotifications(): Promise<boolean> {
95+
const settings = await notifee.requestPermission()
96+
97+
return settings.authorizationStatus >= AuthorizationStatus.AUTHORIZED
98+
}
99+
100+
async displayNotification(options: Notification): Promise<string> {
101+
return await notifee.displayNotification({
102+
...options,
103+
android: {
104+
...options.android,
105+
channelId: 'files',
106+
},
107+
})
108+
}
109+
110+
async cancelNotification(notificationId: string): Promise<void> {
111+
await notifee.cancelNotification(notificationId)
112+
}
79113

80114
async removeRawStorageValuesForIdentifier(identifier: string): Promise<void> {
81115
await this.removeRawStorageValue(namespacedKey(identifier, RawStorageKey.SnjsVersion))

packages/mobile/src/MobileWebAppContainer.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { MobileDevice, MobileDeviceEvent } from './Lib/MobileDevice'
1414
import { IsDev } from './Lib/Utils'
1515
import { ReceivedSharedItemsHandler } from './ReceivedSharedItemsHandler'
1616
import { ReviewService } from './ReviewService'
17+
import notifee, { EventType } from '@notifee/react-native'
1718

1819
const LoggingEnabled = IsDev
1920

@@ -117,6 +118,34 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
117118
}
118119
}, [webViewRef, stateService, device, androidBackHandlerService, colorSchemeService])
119120

121+
useEffect(() => {
122+
return notifee.onForegroundEvent(({ type, detail }) => {
123+
if (type !== EventType.ACTION_PRESS) {
124+
return
125+
}
126+
127+
const { notification, pressAction } = detail
128+
129+
if (!notification || !pressAction) {
130+
return
131+
}
132+
133+
if (pressAction.id !== 'open-file') {
134+
return
135+
}
136+
137+
webViewRef.current?.postMessage(
138+
JSON.stringify({
139+
reactNativeEvent: ReactNativeToWebEvent.OpenFilePreview,
140+
messageType: 'event',
141+
messageData: {
142+
id: notification.id,
143+
},
144+
}),
145+
)
146+
})
147+
}, [])
148+
120149
useEffect(() => {
121150
const observer = device.addMobileDeviceEventReceiver((event) => {
122151
if (event === MobileDeviceEvent.RequestsWebViewReload) {

packages/services/src/Domain/Device/MobileDeviceInterface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { DeviceInterface } from './DeviceInterface'
55
import { AppleIAPReceipt } from '../Subscription/AppleIAPReceipt'
66
import { ApplicationEvent } from '../Event/ApplicationEvent'
77

8+
import type { Notification } from '../../../../mobile/node_modules/@notifee/react-native/dist/index'
9+
810
export interface MobileDeviceInterface extends DeviceInterface {
911
environment: Environment.Mobile
1012
platform: Platform.Ios | Platform.Android
@@ -34,4 +36,8 @@ export interface MobileDeviceInterface extends DeviceInterface {
3436
purchaseSubscriptionIAP(plan: AppleIAPProductId): Promise<AppleIAPReceipt | undefined>
3537
authenticateWithU2F(authenticationOptionsJSONString: string): Promise<Record<string, unknown> | null>
3638
notifyApplicationEvent(event: ApplicationEvent): void
39+
40+
canDisplayNotifications(): Promise<boolean>
41+
displayNotification(options: Notification): Promise<string>
42+
cancelNotification(notificationId: string): Promise<void>
3743
}

packages/snjs/lib/Client/ReactNativeToWebEvent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export enum ReactNativeToWebEvent {
1212
ReceivedFile = 'ReceivedFile',
1313
ReceivedLink = 'ReceivedLink',
1414
ReceivedText = 'ReceivedText',
15+
OpenFilePreview = 'OpenFilePreview',
1516
}

packages/ui-services/src/WebApplication/WebApplicationInterface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface WebApplicationInterface extends ApplicationInterface {
2424
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void
2525
handleReceivedTextEvent(item: { text: string; title?: string }): Promise<void>
2626
handleReceivedLinkEvent(item: { link: string; title: string }): Promise<void>
27+
handleOpenFilePreviewEvent(item: { id: string }): void
2728
isNativeMobileWeb(): boolean
2829
handleAndroidBackButtonPressed(): void
2930
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined

packages/web/src/javascripts/Application/WebApplication.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
NoteContent,
2323
SNNote,
2424
DesktopManagerInterface,
25+
FileItem,
2526
} from '@standardnotes/snjs'
2627
import { action, computed, makeObservable, observable } from 'mobx'
2728
import { startAuthentication, startRegistration } from '@simplewebauthn/browser'
@@ -76,6 +77,7 @@ import { NoAccountWarningController } from '@/Controllers/NoAccountWarningContro
7677
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
7778
import { PersistenceService } from '@/Controllers/Abstract/PersistenceService'
7879
import { removeFromArray } from '@standardnotes/utils'
80+
import { FileItemActionType } from '@/Components/AttachedFilesPopover/PopoverFileItemAction'
7981

8082
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
8183

@@ -353,6 +355,21 @@ export class WebApplication extends SNApplication implements WebApplicationInter
353355
this.notifyWebEvent(WebAppEvent.MobileKeyboardDidChangeFrame, frame)
354356
}
355357

358+
handleOpenFilePreviewEvent({ id }: { id: string }): void {
359+
const file = this.items.findItem<FileItem>(id)
360+
if (!file) {
361+
return
362+
}
363+
this.filesController
364+
.handleFileAction({
365+
type: FileItemActionType.PreviewFile,
366+
payload: {
367+
file,
368+
},
369+
})
370+
.catch(console.error)
371+
}
372+
356373
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void {
357374
const filesController = this.filesController
358375
const blob = getBlobFromBase64(file.data, file.mimeType)

0 commit comments

Comments
 (0)