diff --git a/apps/mobile/app/screens/notes/common.ts b/apps/mobile/app/screens/notes/common.ts index e2d48b606..1907dec3a 100644 --- a/apps/mobile/app/screens/notes/common.ts +++ b/apps/mobile/app/screens/notes/common.ts @@ -17,19 +17,18 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { db } from "../../common/database"; import { DDS } from "../../services/device-detection"; import { eSendEvent } from "../../services/event-manager"; import Navigation from "../../services/navigation"; import { useMenuStore } from "../../stores/use-menu-store"; +import { NotesScreenParams } from "../../stores/use-navigation-store"; import { useTagStore } from "../../stores/use-tag-store"; -import { db } from "../../common/database"; import { eOnLoadNote } from "../../utils/events"; import { openLinkInBrowser } from "../../utils/functions"; import { tabBarRef } from "../../utils/global-refs"; -import { editorController, editorState } from "../editor/tiptap/utils"; -import { NotesScreenParams } from "../../stores/use-navigation-store"; import { TopicType } from "../../utils/types"; -import { ChangeEmail } from "../../components/sheets/change-email"; +import { editorController, editorState } from "../editor/tiptap/utils"; export function toCamelCase(title: string) { return title.slice(0, 1).toUpperCase() + title.slice(1); diff --git a/apps/mobile/app/screens/settings/components.tsx b/apps/mobile/app/screens/settings/components.tsx index 1b8afe04a..7e0e5f700 100644 --- a/apps/mobile/app/screens/settings/components.tsx +++ b/apps/mobile/app/screens/settings/components.tsx @@ -22,12 +22,14 @@ import { AccentColorPicker, HomagePageSelector } from "./appearance"; import { AutomaticBackupsSelector } from "./backup-restore"; import DebugLogs from "./debug"; import { ConfigureToolbar } from "./editor/configure-toolbar"; +import SoundPicker from "./sound-picker"; import { Subscription } from "./subscription"; export const components: { [name: string]: ReactElement } = { - colorpicker: , + colorpicker: , homeselector: , autobackups: , subscription: , configuretoolbar: , - "debug-logs": + "debug-logs": , + "sound-picker": }; diff --git a/apps/mobile/app/screens/settings/settings-data.tsx b/apps/mobile/app/screens/settings/settings-data.tsx index ef5f7c67b..9c3058746 100644 --- a/apps/mobile/app/screens/settings/settings-data.tsx +++ b/apps/mobile/app/screens/settings/settings-data.tsx @@ -68,6 +68,9 @@ import { useDragState } from "./editor/state"; import { verifyUser } from "./functions"; import { SettingSection } from "./types"; import { getTimeLeft } from "./user-section"; +import notifee from "@notifee/react-native"; + +type User = any; export const settingsGroups: SettingSection[] = [ { @@ -878,6 +881,30 @@ export const settingsGroups: SettingSection[] = [ Notifications.setupReminders(); } } + }, + { + id: "reminder-sound", + type: "screen", + name: "Change notification sound", + description: + "Set the notification sound for reminder notifications", + component: "sound-picker", + icon: "bell-ring", + hidden: () => Platform.OS === "android" + }, + { + id: "reminder-sound", + name: "Change notification sound", + description: + "Set the notification sound for reminder notifications", + icon: "bell-ring", + hidden: () => Platform.OS === "ios", + modifer: async () => { + const id = await Notifications.getChannelId("urgent"); + if (id) { + await notifee.openNotificationSettings(id); + } + } } ] } diff --git a/apps/mobile/app/screens/settings/sound-picker.tsx b/apps/mobile/app/screens/settings/sound-picker.tsx new file mode 100644 index 000000000..ec9173c5a --- /dev/null +++ b/apps/mobile/app/screens/settings/sound-picker.tsx @@ -0,0 +1,170 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2022 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from "react"; +import { FlatList, Platform, View } from "react-native"; +import NotificationSounds, { + playSampleSound, + Sound, + stopSampleSound +} from "react-native-notification-sounds"; +import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import { IconButton } from "../../components/ui/icon-button"; +import { PressableButton } from "../../components/ui/pressable"; +import Paragraph from "../../components/ui/typography/paragraph"; +import Notifications from "../../services/notifications"; +import SettingsService from "../../services/settings"; +import { useSettingStore } from "../../stores/use-setting-store"; +import { useThemeStore } from "../../stores/use-theme-store"; +import { SIZE } from "../../utils/size"; + +const SoundItem = ({ + playingSoundId, + selectedSoundId, + item, + index, + setPlaying +}: { + playingSoundId?: string; + selectedSoundId?: string; + item: Sound; + index: number; + setPlaying: (sound: Sound | undefined) => void; +}) => { + const colors = useThemeStore((state) => state.colors); + const isPlaying = playingSoundId === item.soundID; + + return ( + { + SettingsService.set({ + notificationSound: + item.soundID === "defaultSound" + ? undefined + : { + ...item, + platform: Platform.OS + } + }); + Notifications.setupReminders(); + }} + > + + + + {item?.title} + + + + {item.soundID === "defaultSound" ? null : ( + { + if (isPlaying) { + stopSampleSound(); + } else { + playSampleSound(item); + setPlaying(item); + setTimeout(() => { + setPlaying(undefined); + stopSampleSound(); + }, 5 * 1000); + } + }} + /> + )} + + ); +}; + +export default function SoundPicker() { + const [sounds, setSounds] = useState([]); + const [ringtones, setRingtones] = useState([]); + const [playing, setPlaying] = useState(); + const notificationSound = useSettingStore( + (state) => state.settings.notificationSound + ); + + useEffect(() => { + NotificationSounds.getNotifications("notification").then((results) => + setSounds([ + { + soundID: "defaultSound", + title: "Default sound", + url: "" + }, + ...results + ]) + ); + + NotificationSounds.getNotifications("ringtone").then((results) => + setRingtones([ + { + soundID: "defaultSound", + title: "Default sound", + url: "" + }, + ...results + ]) + ); + }, []); + + return ( + + ( + + )} + /> + + ); +} diff --git a/apps/mobile/app/services/notifications.ts b/apps/mobile/app/services/notifications.ts index 73e5cb929..94b210f23 100644 --- a/apps/mobile/app/services/notifications.ts +++ b/apps/mobile/app/services/notifications.ts @@ -280,8 +280,9 @@ async function scheduleNotification( ) continue; const iosProperties: { [name: string]: any } = {}; + const notificationSound = SettingsService.get().notificationSound; if (priority === "urgent") { - iosProperties["sound"] = "default"; + iosProperties["sound"] = notificationSound?.url || "default"; } const reminderTime = SettingsService.get().defaultSnoozeTime; @@ -301,7 +302,6 @@ async function scheduleNotification( } }); } - console.log(trigger); await notifee.createTriggerNotification( { id: trigger.id, @@ -321,6 +321,7 @@ async function scheduleNotification( mainComponent: "notesnook" }, actions: androidActions, + sound: notificationSound?.url, style: !description ? undefined : { @@ -330,6 +331,7 @@ async function scheduleNotification( }, ios: { interruptionLevel: "active", + criticalVolume: 1.0, critical: reminder.priority === "silent" || reminder.priority === "urgent" ? false @@ -363,6 +365,7 @@ function loadNote(id: string, jump: boolean) { } async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") { + const notificationSound = SettingsService.get().notificationSound; switch (id) { case "default": return await notifee.createChannel({ @@ -378,15 +381,18 @@ async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") { case "vibrate": return await notifee.createChannel({ id: "com.streetwriters.notesnook.silent", - name: "Silent", + name: "Vibrate", vibration: true }); case "urgent": return await notifee.createChannel({ id: "com.streetwriters.notesnook.urgent", name: "Urgent", + description: + "This channel is used to show notifications with sound & vibration.", vibration: true, - sound: "default" + sound: notificationSound?.url || "default", + bypassDnd: true }); } } @@ -775,7 +781,8 @@ const Notifications = { getScheduledNotificationIds, checkAndRequestPermissions, clearAllTriggers, - setupReminders + setupReminders, + getChannelId }; export default Notifications; diff --git a/apps/mobile/app/stores/use-setting-store.ts b/apps/mobile/app/stores/use-setting-store.ts index 2538778c7..0e353494d 100644 --- a/apps/mobile/app/stores/use-setting-store.ts +++ b/apps/mobile/app/stores/use-setting-store.ts @@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { Dimensions } from "react-native"; +import { Dimensions,PlatformOSType } from "react-native"; import Config from "react-native-config"; import { FileType } from "react-native-scoped-storage"; import create, { State } from "zustand"; import { ACCENT } from "../utils/color-scheme"; import { initialWindowMetrics } from "react-native-safe-area-context"; import { Reminder } from "../services/notifications"; +import { Sound } from "react-native-notification-sounds"; export type Settings = { showToolbarOnTop?: boolean; @@ -70,7 +71,8 @@ export type Settings = { defaultSnoozeTime?: string; reminderNotificationMode: Reminder["priority"]; corsProxy: string; - disableRealtimeSync?:boolean + disableRealtimeSync?: boolean; + notificationSound?: Sound & { platform: PlatformOSType }; }; type DimensionsType = { @@ -146,7 +148,8 @@ export const useSettingStore = create((set) => ({ reminderNotifications: true, defaultSnoozeTime: "5", corsProxy: "https://cors.notesnook.com", - reminderNotificationMode: "urgent" + reminderNotificationMode: "urgent", + notificationSound: undefined }, sheetKeyboardHandler: true, fullscreen: false, diff --git a/apps/mobile/native/package.json b/apps/mobile/native/package.json index 76330aff9..9cc1770a9 100644 --- a/apps/mobile/native/package.json +++ b/apps/mobile/native/package.json @@ -56,7 +56,8 @@ "@ammarahmed/notifee-react-native": "7.3.1", "react-native-modal-datetime-picker":"14.0.0", "@react-native-community/datetimepicker":"6.6.0", - "react-native-date-picker": "4.2.6" + "react-native-date-picker": "4.2.6", + "react-native-notification-sounds": "0.5.5" }, "devDependencies": { diff --git a/apps/mobile/package-lock.json b/apps/mobile/package-lock.json index cdc75779c..fe2bbfc2c 100644 --- a/apps/mobile/package-lock.json +++ b/apps/mobile/package-lock.json @@ -98,6 +98,7 @@ "react-native-keychain": "4.0.5", "react-native-mmkv-storage": "^0.8.0", "react-native-modal-datetime-picker": "14.0.0", + "react-native-notification-sounds": "0.5.5", "react-native-orientation": "https://github.com/yamill/react-native-orientation.git", "react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git", "react-native-reanimated": "2.13.0", @@ -18424,6 +18425,15 @@ "react-native": ">=0.65.0" } }, + "node_modules/react-native-notification-sounds": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz", + "integrity": "sha512-Pw4mRDbusYVmi8+Cwxcpx1BI9gwFY4CpWx8N+gMNDd1n7WRJS/Shi3Igo1luo3zLPkVVrpyZbpuZaE8cG9Q3Cg==", + "peerDependencies": { + "react": "^16.8.1", + "react-native": ">=0.64.1" + } + }, "node_modules/react-native-orientation": { "version": "3.1.3", "resolved": "git+ssh://git@github.com/yamill/react-native-orientation.git#b45830cce0837fa668838554e023979497673c82", @@ -24392,6 +24402,7 @@ "react-native-keychain": "4.0.5", "react-native-mmkv-storage": "^0.8.0", "react-native-modal-datetime-picker": "14.0.0", + "react-native-notification-sounds": "0.5.5", "react-native-orientation": "https://github.com/yamill/react-native-orientation.git", "react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git", "react-native-reanimated": "2.13.0", @@ -35176,6 +35187,11 @@ "prop-types": "^15.7.2" } }, + "react-native-notification-sounds": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz", + "integrity": "sha512-Pw4mRDbusYVmi8+Cwxcpx1BI9gwFY4CpWx8N+gMNDd1n7WRJS/Shi3Igo1luo3zLPkVVrpyZbpuZaE8cG9Q3Cg==" + }, "react-native-orientation": { "version": "git+ssh://git@github.com/yamill/react-native-orientation.git#b45830cce0837fa668838554e023979497673c82", "from": "react-native-orientation@https://github.com/yamill/react-native-orientation.git"