diff --git a/App.tsx b/App.tsx index 6714496..ae12cca 100644 --- a/App.tsx +++ b/App.tsx @@ -14,7 +14,7 @@ import { } from 'react-native-paper'; import {SQLiteDatabase} from 'react-native-sqlite-storage'; import Ionicon from 'react-native-vector-icons/Ionicons'; -import {createPlans, createSets, createSettings, getDb} from './db'; +import {addSound, createPlans, createSets, createSettings, getDb} from './db'; import Routes from './Routes'; export const Drawer = createDrawerNavigator(); @@ -58,6 +58,7 @@ const App = () => { await _db.executeSql(createPlans); await _db.executeSql(createSets); await _db.executeSql(createSettings); + await _db.executeSql(addSound).catch(() => null); setDb(_db); const [result] = await _db.executeSql(`SELECT * FROM settings LIMIT 1`); if (result.rows.length === 0) diff --git a/EditSet.tsx b/EditSet.tsx index cd67b9e..8eb8a95 100644 --- a/EditSet.tsx +++ b/EditSet.tsx @@ -36,7 +36,11 @@ export default function EditSet() { const settings: Settings = result.rows.item(0); if (!settings.alarm) return; const milliseconds = settings.minutes * 60 * 1000 + settings.seconds * 1000; - NativeModules.AlarmModule.timer(milliseconds, !!settings.vibrate); + NativeModules.AlarmModule.timer( + milliseconds, + !!settings.vibrate, + settings.sound, + ); }, [db]); const update = useCallback( diff --git a/SettingsPage.tsx b/SettingsPage.tsx index a0fa51f..77ec31a 100644 --- a/SettingsPage.tsx +++ b/SettingsPage.tsx @@ -6,11 +6,12 @@ import React, { useState, } from 'react'; import {NativeModules, StyleSheet, Text, View} from 'react-native'; -import {Searchbar, TextInput} from 'react-native-paper'; +import {Button, Searchbar, TextInput} from 'react-native-paper'; import {DatabaseContext, SnackbarContext} from './App'; import ConfirmDialog from './ConfirmDialog'; import MassiveSwitch from './MassiveSwitch'; import Settings from './settings'; +import DocumentPicker from 'react-native-document-picker'; export default function SettingsPage() { const [vibrate, setVibrate] = useState(true); @@ -19,6 +20,7 @@ export default function SettingsPage() { const [seconds, setSeconds] = useState(''); const [alarm, setAlarm] = useState(false); const [predictive, setPredictive] = useState(false); + const [sound, setSound] = useState(''); const [battery, setBattery] = useState(false); const [ignoring, setIgnoring] = useState(false); const [search, setSearch] = useState(''); @@ -35,6 +37,7 @@ export default function SettingsPage() { setPredictive(!!settings.predict); setMaxSets(settings.sets.toString()); setVibrate(!!settings.vibrate); + setSound(settings.sound); NativeModules.AlarmModule.ignoringBattery(setIgnoring); }, [db]); @@ -44,10 +47,10 @@ export default function SettingsPage() { useEffect(() => { db.executeSql( - `UPDATE settings SET vibrate=?,minutes=?,sets=?,seconds=?,alarm=?,predict=?`, - [vibrate, minutes, maxSets, seconds, alarm, predictive], + `UPDATE settings SET vibrate=?,minutes=?,sets=?,seconds=?,alarm=?,predict=?,sound=?`, + [vibrate, minutes, maxSets, seconds, alarm, predictive, sound], ); - }, [vibrate, minutes, maxSets, seconds, alarm, predictive, db]); + }, [vibrate, minutes, maxSets, seconds, alarm, predictive, sound, db]); const changeAlarmEnabled = useCallback( (enabled: boolean) => { @@ -72,6 +75,14 @@ export default function SettingsPage() { [setVibrate], ); + const changeSound = useCallback(async () => { + const {fileCopyUri} = await DocumentPicker.pickSingle({ + type: 'audio/*', + copyTo: 'documentDirectory', + }); + if (fileCopyUri) setSound(fileCopyUri); + }, []); + const items: {name: string; element: ReactNode}[] = [ { name: 'Sets per workout', @@ -162,6 +173,15 @@ export default function SettingsPage() { ), }, + { + name: 'Alarm sound', + element: ( + + ), + }, ]; return ( diff --git a/android/app/src/main/java/com/massive/AlarmModule.kt b/android/app/src/main/java/com/massive/AlarmModule.kt index 9539c34..5601faf 100644 --- a/android/app/src/main/java/com/massive/AlarmModule.kt +++ b/android/app/src/main/java/com/massive/AlarmModule.kt @@ -23,11 +23,12 @@ class AlarmModule internal constructor(context: ReactApplicationContext?) : @RequiresApi(api = Build.VERSION_CODES.O) @ReactMethod - fun timer(milliseconds: Int, vibrate: Boolean) { + fun timer(milliseconds: Int, vibrate: Boolean, sound: String?) { Log.d("AlarmModule", "Queue alarm for $milliseconds delay") val intent = Intent(reactApplicationContext, TimerService::class.java) intent.putExtra("milliseconds", milliseconds) intent.putExtra("vibrate", vibrate) + intent.putExtra("sound", sound) reactApplicationContext.startService(intent) } diff --git a/android/app/src/main/java/com/massive/AlarmService.kt b/android/app/src/main/java/com/massive/AlarmService.kt index 273ac8f..8d16610 100644 --- a/android/app/src/main/java/com/massive/AlarmService.kt +++ b/android/app/src/main/java/com/massive/AlarmService.kt @@ -7,6 +7,7 @@ import android.os.Vibrator import androidx.annotation.RequiresApi import android.content.Intent import android.media.AudioAttributes +import android.net.Uri import android.os.Build import android.os.VibrationEffect import android.os.IBinder @@ -21,9 +22,25 @@ class AlarmService : Service(), OnPreparedListener { onDestroy() return START_STICKY } - mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon) - mediaPlayer?.start() - mediaPlayer?.setOnCompletionListener { vibrator?.cancel() } + val sound = intent.extras?.getString("sound") + if (sound == null) { + mediaPlayer = MediaPlayer.create(applicationContext, R.raw.argon) + mediaPlayer?.start() + mediaPlayer?.setOnCompletionListener { vibrator?.cancel() } + } else { + mediaPlayer = MediaPlayer().apply { + setAudioAttributes( + AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributes.USAGE_MEDIA) + .build() + ) + setDataSource(applicationContext, Uri.parse(sound)) + prepare() + start() + setOnCompletionListener { vibrator?.cancel() } + } + } val pattern = longArrayOf(0, 300, 1300, 300, 1300, 300) vibrator = applicationContext.getSystemService(VIBRATOR_SERVICE) as Vibrator val audioAttributes = AudioAttributes.Builder() diff --git a/android/app/src/main/java/com/massive/TimerService.kt b/android/app/src/main/java/com/massive/TimerService.kt index ba167c8..517ea36 100644 --- a/android/app/src/main/java/com/massive/TimerService.kt +++ b/android/app/src/main/java/com/massive/TimerService.kt @@ -19,12 +19,14 @@ class TimerService : Service() { private var currentMs: Long? = null private var countdownTimer: CountDownTimer? = null private var vibrate: Boolean = true + private var sound: String? = null @RequiresApi(Build.VERSION_CODES.O) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d("TimerService", "Started timer service.") Log.d("TimerService", "endMs=$endMs,currentMs=$currentMs") vibrate = intent!!.extras!!.getBoolean("vibrate") + sound = intent.extras?.getString("sound") if (intent.action == "add") { manager?.cancel(NOTIFICATION_ID_DONE) endMs = currentMs!!.toInt().plus(60000) @@ -80,6 +82,7 @@ class TimerService : Service() { manager?.cancel(NOTIFICATION_ID_PENDING) val alarmIntent = Intent(applicationContext, AlarmService::class.java) alarmIntent.putExtra("vibrate", vibrate) + alarmIntent.putExtra("sound", sound) applicationContext.startService(alarmIntent) } } diff --git a/db.ts b/db.ts index 62994c8..ac7cc73 100644 --- a/db.ts +++ b/db.ts @@ -32,3 +32,7 @@ export const createSettings = ` sets INTEGER NOT NULL DEFAULT 3 ); `; + +export const addSound = ` + ALTER TABLE settings ADD COLUMN sound TEXT NULL; +`; diff --git a/settings.ts b/settings.ts index c185665..8fac1e3 100644 --- a/settings.ts +++ b/settings.ts @@ -5,4 +5,5 @@ export default interface Settings { vibrate: number; predict: number; sets: number; + sound: string; }