Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
9 changes: 9 additions & 0 deletions packages/mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ PODS:
- React-Core
- RNKeychain (8.1.2):
- React-Core
- RNNotifee (7.8.0):
- React-Core
- RNNotifee/NotifeeCore (= 7.8.0)
- RNNotifee/NotifeeCore (7.8.0):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
- RNShare (9.4.1):
Expand Down Expand Up @@ -593,6 +598,7 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNIap (from `../node_modules/react-native-iap`)
- RNKeychain (from `../node_modules/react-native-keychain`)
- "RNNotifee (from `../node_modules/@notifee/react-native`)"
- RNPrivacySnapshot (from `../node_modules/react-native-privacy-snapshot`)
- RNShare (from `../node_modules/react-native-share`)
- RNStoreReview (from `../node_modules/react-native-store-review`)
Expand Down Expand Up @@ -716,6 +722,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-iap"
RNKeychain:
:path: "../node_modules/react-native-keychain"
RNNotifee:
:path: "../node_modules/@notifee/react-native"
RNPrivacySnapshot:
:path: "../node_modules/react-native-privacy-snapshot"
RNShare:
Expand Down Expand Up @@ -789,6 +797,7 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNIap: c397f49db45af3b10dca64b2325f21bb8078ad21
RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c
RNNotifee: f3c01b391dd8e98e67f539f9a35a9cbcd3bae744
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNShare: 32e97adc8d8c97d4a26bcdd3c45516882184f8b6
RNStoreReview: 923b1c888c13469925bf0256dc2c046eab557ce5
Expand Down
1 change: 1 addition & 0 deletions packages/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"node": ">=16"
},
"dependencies": {
"@notifee/react-native": "^7.8.0",
"react-native-store-review": "^0.4.1"
}
}
36 changes: 35 additions & 1 deletion packages/mobile/src/Lib/MobileDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { Database } from './Database/Database'
import { isLegacyIdentifier } from './Database/LegacyIdentifier'
import { LegacyKeyValueStore } from './Database/LegacyKeyValueStore'
import Keychain from './Keychain'
import notifee, { AuthorizationStatus, Notification } from '@notifee/react-native'

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

Expand All @@ -75,7 +76,40 @@ export class MobileDevice implements MobileDeviceInterface {
private stateObserverService?: AppStateObserverService,
private androidBackHandlerService?: AndroidBackHandlerService,
private colorSchemeService?: ColorSchemeObserverService,
) {}
) {
this.initializeNotifications().catch(console.error)
}

async initializeNotifications() {
if (Platform.OS !== 'android') {
return
}

await notifee.createChannel({
id: 'files',
name: 'File Upload/Download',
})
}

async canDisplayNotifications(): Promise<boolean> {
const settings = await notifee.requestPermission()

return settings.authorizationStatus >= AuthorizationStatus.AUTHORIZED
}

async displayNotification(options: Notification): Promise<string> {
return await notifee.displayNotification({
...options,
android: {
...options.android,
channelId: 'files',
},
})
}

async cancelNotification(notificationId: string): Promise<void> {
await notifee.cancelNotification(notificationId)
}

async removeRawStorageValuesForIdentifier(identifier: string): Promise<void> {
await this.removeRawStorageValue(namespacedKey(identifier, RawStorageKey.SnjsVersion))
Expand Down
29 changes: 29 additions & 0 deletions packages/mobile/src/MobileWebAppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MobileDevice, MobileDeviceEvent } from './Lib/MobileDevice'
import { IsDev } from './Lib/Utils'
import { ReceivedSharedItemsHandler } from './ReceivedSharedItemsHandler'
import { ReviewService } from './ReviewService'
import notifee, { EventType } from '@notifee/react-native'

const LoggingEnabled = IsDev

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

useEffect(() => {
return notifee.onForegroundEvent(({ type, detail }) => {
if (type !== EventType.ACTION_PRESS) {
return
}

const { notification, pressAction } = detail

if (!notification || !pressAction) {
return
}

if (pressAction.id !== 'open-file') {
return
}

webViewRef.current?.postMessage(
JSON.stringify({
reactNativeEvent: ReactNativeToWebEvent.OpenFilePreview,
messageType: 'event',
messageData: {
id: notification.id,
},
}),
)
})
}, [])

useEffect(() => {
const observer = device.addMobileDeviceEventReceiver((event) => {
if (event === MobileDeviceEvent.RequestsWebViewReload) {
Expand Down
6 changes: 6 additions & 0 deletions packages/services/src/Domain/Device/MobileDeviceInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { DeviceInterface } from './DeviceInterface'
import { AppleIAPReceipt } from '../Subscription/AppleIAPReceipt'
import { ApplicationEvent } from '../Event/ApplicationEvent'

import type { Notification } from '../../../../mobile/node_modules/@notifee/react-native/dist/index'

export interface MobileDeviceInterface extends DeviceInterface {
environment: Environment.Mobile
platform: Platform.Ios | Platform.Android
Expand Down Expand Up @@ -34,4 +36,8 @@ export interface MobileDeviceInterface extends DeviceInterface {
purchaseSubscriptionIAP(plan: AppleIAPProductId): Promise<AppleIAPReceipt | undefined>
authenticateWithU2F(authenticationOptionsJSONString: string): Promise<Record<string, unknown> | null>
notifyApplicationEvent(event: ApplicationEvent): void

canDisplayNotifications(): Promise<boolean>
displayNotification(options: Notification): Promise<string>
cancelNotification(notificationId: string): Promise<void>
}
1 change: 1 addition & 0 deletions packages/snjs/lib/Client/ReactNativeToWebEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export enum ReactNativeToWebEvent {
ReceivedFile = 'ReceivedFile',
ReceivedLink = 'ReceivedLink',
ReceivedText = 'ReceivedText',
OpenFilePreview = 'OpenFilePreview',
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void
handleReceivedTextEvent(item: { text: string; title?: string }): Promise<void>
handleReceivedLinkEvent(item: { link: string; title: string }): Promise<void>
handleOpenFilePreviewEvent(item: { id: string }): void
isNativeMobileWeb(): boolean
handleAndroidBackButtonPressed(): void
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined
Expand Down
17 changes: 17 additions & 0 deletions packages/web/src/javascripts/Application/WebApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
NoteContent,
SNNote,
DesktopManagerInterface,
FileItem,
} from '@standardnotes/snjs'
import { action, computed, makeObservable, observable } from 'mobx'
import { startAuthentication, startRegistration } from '@simplewebauthn/browser'
Expand Down Expand Up @@ -76,6 +77,7 @@ import { NoAccountWarningController } from '@/Controllers/NoAccountWarningContro
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
import { PersistenceService } from '@/Controllers/Abstract/PersistenceService'
import { removeFromArray } from '@standardnotes/utils'
import { FileItemActionType } from '@/Components/AttachedFilesPopover/PopoverFileItemAction'

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

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

handleOpenFilePreviewEvent({ id }: { id: string }): void {
const file = this.items.findItem<FileItem>(id)
if (!file) {
return
}
this.filesController
.handleFileAction({
type: FileItemActionType.PreviewFile,
payload: {
file,
},
})
.catch(console.error)
}

handleReceivedFileEvent(file: { name: string; mimeType: string; data: string }): void {
const filesController = this.filesController
const blob = getBlobFromBase64(file.data, file.mimeType)
Expand Down
Loading