From cf29516c00aaf9925b78a67a482a1d2bb3647f57 Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Thu, 20 Jul 2023 03:34:04 +0300 Subject: [PATCH 01/18] allow user to set sleep time --- src/localisation/lang/en/locale.json | 1 + src/localisation/lang/nl/locale.json | 1 + src/localisation/types.ts | 1 + src/screens/Settings/index.tsx | 11 +++++++- src/screens/Settings/stacks/Timer.tsx | 37 +++++++++++++++++++++++++++ src/screens/Settings/types.ts | 1 + src/store/index.ts | 12 +++++++++ src/store/settings/actions.ts | 3 +++ src/store/settings/index.ts | 16 +++++++++++- 9 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/screens/Settings/stacks/Timer.tsx diff --git a/src/localisation/lang/en/locale.json b/src/localisation/lang/en/locale.json index bbf005b..788c9fa 100644 --- a/src/localisation/lang/en/locale.json +++ b/src/localisation/lang/en/locale.json @@ -41,6 +41,7 @@ "more-info": "More Info", "track": "Track", "playlists": "Playlists", + "timer": "Set Sleep Timer", "playlist": "Playlist", "play-playlist": "Play Playlist", "shuffle-album": "Shuffle Album", diff --git a/src/localisation/lang/nl/locale.json b/src/localisation/lang/nl/locale.json index 485d822..67cef3e 100644 --- a/src/localisation/lang/nl/locale.json +++ b/src/localisation/lang/nl/locale.json @@ -41,6 +41,7 @@ "more-info": "Meer informatie", "track": "Track", "playlists": "Playlists", + "timer": "Set Sleep Timer", "playlist": "Playlist", "play-playlist": "Speel Playlist", "shuffle-album": "Shuffle Album", diff --git a/src/localisation/types.ts b/src/localisation/types.ts index 34e431d..5f85594 100644 --- a/src/localisation/types.ts +++ b/src/localisation/types.ts @@ -69,3 +69,4 @@ export type LocaleKeys = 'play-next' | 'color-scheme-dark' | 'artists' | 'privacy-policy' +| 'timer' diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index aeb2e77..4738aa6 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { t } from '@/localisation'; import { createStackNavigator } from '@react-navigation/stack'; import { useNavigation } from '@react-navigation/native'; @@ -16,8 +16,12 @@ import ColorScheme from './stacks/ColorScheme'; import PlaybackReporting from './stacks/PlaybackReporting'; import { SafeScrollView } from '@/components/SafeNavigatorView'; import PrivacyPolicy from './components/PrivacyPolicy'; +import Timer from './stacks/Timer'; +import { Paragraph, Text } from '@/components/Typography'; +import { useTypedSelector } from '@/store'; export function SettingsList() { + const defaultStyles = useDefaultStyles(); const navigation = useNavigation(); const handleLibraryClick = useCallback(() => { navigation.navigate('Library'); }, [navigation]); const handleCacheClick = useCallback(() => { navigation.navigate('Cache'); }, [navigation]); @@ -25,10 +29,14 @@ export function SettingsList() { const handlePlaybackReportingClick = useCallback(() => { navigation.navigate('Playback Reporting'); }, [navigation]); const handleColorSchemeClick = useCallback(() => { navigation.navigate('Color Scheme'); }, [navigation]); const handlePrivacyPolicyClick = useCallback(() => { navigation.navigate('PrivacyPolicy'); }, [navigation]); + const handleTimerClick = useCallback(() => { navigation.navigate('Timer'); }, [navigation]); + + const { sleepTime } = useTypedSelector(state => state.settings); return ( {t('jellyfin-library')} + {t('timer')} Set Time: {sleepTime} {t('setting-cache')} {t('error-reporting')} {t('playback-reporting')} @@ -52,6 +60,7 @@ export default function Settings() { }}> + diff --git a/src/screens/Settings/stacks/Timer.tsx b/src/screens/Settings/stacks/Timer.tsx new file mode 100644 index 0000000..b63b94e --- /dev/null +++ b/src/screens/Settings/stacks/Timer.tsx @@ -0,0 +1,37 @@ +import React, { useCallback, useState } from 'react'; +import Container from '../components/Container'; +import { Text } from '@/components/Typography'; +import { InputContainer } from '../components/Input'; +import Input from '@/components/Input'; +import useDefaultStyles from '@/components/Colors'; +import { setSleepTime } from '@/store/settings/actions'; +import { useNavigation } from '@react-navigation/native'; +import { useDispatch } from 'react-redux'; + +function Timer() { + const defaultStyles = useDefaultStyles(); + + const navigation = useNavigation(); + const dispatch = useDispatch(); + + const setSleeper = useCallback((sleepTime) => { + dispatch(setSleepTime(Number.parseInt(sleepTime))); + }, [navigation, dispatch]); + + return ( + + + Set Sleep Timer (min) + + + Set this to automatically stop the audio when time runs out. + + + ); +} + +export default Timer; \ No newline at end of file diff --git a/src/screens/Settings/types.ts b/src/screens/Settings/types.ts index a915946..246e41c 100644 --- a/src/screens/Settings/types.ts +++ b/src/screens/Settings/types.ts @@ -6,6 +6,7 @@ export type SettingsStackParams = { Library: undefined; Cache: undefined; Sentry: undefined; + Timer: undefined; }; export type SettingsNavigationProp = StackNavigationProp; diff --git a/src/store/index.ts b/src/store/index.ts index 195a775..e70428e 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -55,6 +55,18 @@ const persistConfig: PersistConfig> = { } }; }, + // @ts-expect-error migrations are poorly typed + 4: (state: AppState) => { + return { + ...state, + settings: { + ...state.settings, + enableSleepTimer: false, + sleepTime: 60, + remainingSleepTime: 0 + } + }; + }, }) }; diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index 7a5bcad..029ef86 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -7,3 +7,6 @@ export const setOnboardingStatus = createAction('SET_ONBOARDING_STATUS' export const setReceivedErrorReportingAlert = createAction('SET_RECEIVED_ERROR_REPORTING_ALERT'); export const setEnablePlaybackReporting = createAction('SET_ENABLE_PLAYBACK_REPORTING'); export const setColorScheme = createAction('SET_COLOR_SCHEME'); +export const setSleepTime = createAction('SET_SLEEP_TIME'); +export const setEnableSleepTimer = createAction('SET_ENABLE_SLEEP_TIMER'); +export const setRemainingSleepTime = createAction('SET_REMAINING_SLEEP_TIME'); diff --git a/src/store/settings/index.ts b/src/store/settings/index.ts index fd7a183..6e0dd32 100644 --- a/src/store/settings/index.ts +++ b/src/store/settings/index.ts @@ -1,5 +1,5 @@ import { createReducer } from '@reduxjs/toolkit'; -import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting, setColorScheme } from './actions'; +import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting, setColorScheme, setSleepTime, setEnableSleepTimer, setRemainingSleepTime } from './actions'; import { ColorScheme } from './types'; interface State { @@ -14,6 +14,7 @@ interface State { hasReceivedErrorReportingAlert: boolean; enablePlaybackReporting: boolean; colorScheme: ColorScheme; + sleepTime: number; } const initialState: State = { @@ -22,6 +23,7 @@ const initialState: State = { hasReceivedErrorReportingAlert: false, enablePlaybackReporting: true, colorScheme: ColorScheme.System, + sleepTime: 60, }; const settings = createReducer(initialState, builder => { @@ -49,6 +51,18 @@ const settings = createReducer(initialState, builder => { ...state, colorScheme: action.payload, })); + builder.addCase(setSleepTime, (state, action) => ({ + ...state, + sleepTime: action.payload, + })); + builder.addCase(setEnableSleepTimer, (state, action) => ({ + ...state, + enableSleepTimer: action.payload, + })); + builder.addCase(setRemainingSleepTime, (state, action) => ({ + ...state, + remainingSleepTime: action.payload, + })); }); export default settings; \ No newline at end of file From 9cbc5a26ba0b9b0f35ae88ecb4bd3136148bf593 Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Fri, 21 Jul 2023 13:07:10 +0300 Subject: [PATCH 02/18] Update: allow user to set time --- package-lock.json | 11 +++ package.json | 1 + src/components/ListButton.tsx | 23 ++++- src/screens/Settings/index.tsx | 23 ++++- src/screens/Settings/stacks/Timer.tsx | 37 -------- src/screens/Settings/stacks/timer/Timer.tsx | 90 ++++++++++++++++++++ src/screens/Settings/stacks/timer/styles.tsx | 21 +++++ src/store/index.ts | 2 +- 8 files changed, 165 insertions(+), 43 deletions(-) delete mode 100644 src/screens/Settings/stacks/Timer.tsx create mode 100644 src/screens/Settings/stacks/timer/Timer.tsx create mode 100644 src/screens/Settings/stacks/timer/styles.tsx diff --git a/package-lock.json b/package-lock.json index 67f580f..16c944c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "react-native-reanimated": "^3.6.2", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", + "react-native-select-dropdown": "^3.3.4", "react-native-shadow-2": "^7.0.6", "react-native-skia": "^0.0.1", "react-native-svg": "^13.5.0", @@ -13762,6 +13763,11 @@ "react-native": "*" } }, + "node_modules/react-native-select-dropdown": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/react-native-select-dropdown/-/react-native-select-dropdown-3.3.4.tgz", + "integrity": "sha512-Ld6BGGgCnbiv7uORAP+KnvDQiqeuqdlasKk9woJH9XtFMD44rwjKwelGzsDxFUM9hIAwZdac4UAFmOmXRaMeRg==" + }, "node_modules/react-native-shadow-2": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/react-native-shadow-2/-/react-native-shadow-2-7.0.6.tgz", @@ -26664,6 +26670,11 @@ "warn-once": "^0.1.0" } }, + "react-native-select-dropdown": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/react-native-select-dropdown/-/react-native-select-dropdown-3.3.4.tgz", + "integrity": "sha512-Ld6BGGgCnbiv7uORAP+KnvDQiqeuqdlasKk9woJH9XtFMD44rwjKwelGzsDxFUM9hIAwZdac4UAFmOmXRaMeRg==" + }, "react-native-shadow-2": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/react-native-shadow-2/-/react-native-shadow-2-7.0.6.tgz", diff --git a/package.json b/package.json index 2e0eed1..bcbc2a5 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-native-reanimated": "^3.6.2", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", + "react-native-select-dropdown": "^3.3.4", "react-native-shadow-2": "^7.0.6", "react-native-skia": "^0.0.1", "react-native-svg": "^13.5.0", diff --git a/src/components/ListButton.tsx b/src/components/ListButton.tsx index 5abfc19..1f05fd6 100644 --- a/src/components/ListButton.tsx +++ b/src/components/ListButton.tsx @@ -1,9 +1,10 @@ import React, { useCallback, useState } from 'react'; -import { TouchableOpacityProps } from 'react-native'; +import { StyleSheet, TouchableOpacityProps, View } from 'react-native'; import ChevronRight from '@/assets/icons/chevron-right.svg'; import styled from 'styled-components/native'; import { THEME_COLOR } from '@/CONSTANTS'; import useDefaultStyles from './Colors'; +import { Text } from './Typography'; const BUTTON_SIZE = 14; @@ -19,10 +20,28 @@ const Container = styled.Pressable<{ active?: boolean }>` const Label = styled.Text<{ active?: boolean }>` color: ${THEME_COLOR}; font-size: 16px; + display: flex; `; +function generateListButtonStyles() { + const styles = useDefaultStyles(); + return StyleSheet.create({ + ...styles, + descriptionNote: { + display: 'flex', + flexDirection: 'column' + }, + showDescription: { + display: 'flex' + }, + hideDescription: { + display: 'none' + } + }) +} + const ListButton: React.FC = ({ children, ...props }) => { - const defaultStyles = useDefaultStyles(); + const defaultStyles = generateListButtonStyles(); const [isPressed, setPressed] = useState(false); const handlePressIn = useCallback(() => setPressed(true), []); const handlePressOut = useCallback(() => setPressed(false), []); diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 4738aa6..3782895 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -16,12 +16,11 @@ import ColorScheme from './stacks/ColorScheme'; import PlaybackReporting from './stacks/PlaybackReporting'; import { SafeScrollView } from '@/components/SafeNavigatorView'; import PrivacyPolicy from './components/PrivacyPolicy'; -import Timer from './stacks/Timer'; +import Timer from './stacks/timer/Timer'; import { Paragraph, Text } from '@/components/Typography'; import { useTypedSelector } from '@/store'; export function SettingsList() { - const defaultStyles = useDefaultStyles(); const navigation = useNavigation(); const handleLibraryClick = useCallback(() => { navigation.navigate('Library'); }, [navigation]); const handleCacheClick = useCallback(() => { navigation.navigate('Cache'); }, [navigation]); @@ -33,10 +32,28 @@ export function SettingsList() { const { sleepTime } = useTypedSelector(state => state.settings); + const getTime = () => { + if (!Number.isNaN(sleepTime)) { + const hours = Math.round(sleepTime / 3600); + const timeRemaining = sleepTime % 3600; + let minutes = 0; + + if (timeRemaining > 60) { + minutes = Math.round(timeRemaining / 60); + } + return `${hours} hrs ${minutes} min`; + } else { + return 0; + } + } + return ( {t('jellyfin-library')} - {t('timer')} Set Time: {sleepTime} + + {t('timer')} + {`Set Time: ${getTime()}s`} + {t('setting-cache')} {t('error-reporting')} {t('playback-reporting')} diff --git a/src/screens/Settings/stacks/Timer.tsx b/src/screens/Settings/stacks/Timer.tsx deleted file mode 100644 index b63b94e..0000000 --- a/src/screens/Settings/stacks/Timer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import Container from '../components/Container'; -import { Text } from '@/components/Typography'; -import { InputContainer } from '../components/Input'; -import Input from '@/components/Input'; -import useDefaultStyles from '@/components/Colors'; -import { setSleepTime } from '@/store/settings/actions'; -import { useNavigation } from '@react-navigation/native'; -import { useDispatch } from 'react-redux'; - -function Timer() { - const defaultStyles = useDefaultStyles(); - - const navigation = useNavigation(); - const dispatch = useDispatch(); - - const setSleeper = useCallback((sleepTime) => { - dispatch(setSleepTime(Number.parseInt(sleepTime))); - }, [navigation, dispatch]); - - return ( - - - Set Sleep Timer (min) - - - Set this to automatically stop the audio when time runs out. - - - ); -} - -export default Timer; \ No newline at end of file diff --git a/src/screens/Settings/stacks/timer/Timer.tsx b/src/screens/Settings/stacks/timer/Timer.tsx new file mode 100644 index 0000000..b02bcfc --- /dev/null +++ b/src/screens/Settings/stacks/timer/Timer.tsx @@ -0,0 +1,90 @@ +import React, { useCallback, useState } from 'react'; +import Container from '../../components/Container'; +import { Text } from '@/components/Typography'; +import { InputContainer } from '../../components/Input'; +import Input from '@/components/Input'; +import useDefaultStyles from '@/components/Colors'; +import { setSleepTime } from '@/store/settings/actions'; +import { useNavigation } from '@react-navigation/native'; +import { useDispatch } from 'react-redux'; +import { ScrollView, ToastAndroid, View } from 'react-native'; +import { generateTimerStyles } from './styles'; +import { useTypedSelector } from '@/store'; +import SelectableFilter from '@/screens/Search/stacks/Search/components/SelectableFilter'; +import MicrophoneIcon from '@/assets/icons/microphone.svg'; +import AlbumIcon from '@/assets/icons/collection.svg'; +import TrackIcon from '@/assets/icons/note.svg'; +import SelectDropdown from 'react-native-select-dropdown'; +import { time } from 'console'; + +function Timer() { + const { sleepTime } = useTypedSelector(state => state.settings); + + const [minutes, setMinutes] = useState(); + const [hours, setHours] = useState(); + + const timerStyles = generateTimerStyles(); + + const navigation = useNavigation(); + const dispatch = useDispatch(); + + const setSleeper = useCallback((value: number, timeType: string) => { + if (timeType == 'Hours' && value > 0) { + dispatch(setSleepTime(value * 3600)); + } else if (timeType == 'Minutes' && value > 0) { + dispatch(setSleepTime(value * 60)); + } + }, [navigation, dispatch]); + + const getTime = () => { + if (!Number.isNaN(sleepTime)) { + const hours = Math.round(sleepTime / 3600); + const timeRemaining = sleepTime % 3600; + let minutes = 0; + + if (timeRemaining > 60) { + minutes = Math.round(timeRemaining / 60); + } + return `${hours} hrs ${minutes} min`; + } else { + return 0; + } + } + + return ( + + + Set Sleep Timer. Time Set Previously: {getTime()} + + + H: + { + setSleeper(parseInt(value), 'Hours'); + }} + /> + + + M: + { + setSleeper(parseInt(value), 'Minutes'); + }} + /> + + + Set this to automatically stop the audio when time runs out. + + + ); +} + +export default Timer; \ No newline at end of file diff --git a/src/screens/Settings/stacks/timer/styles.tsx b/src/screens/Settings/stacks/timer/styles.tsx new file mode 100644 index 0000000..78f0c44 --- /dev/null +++ b/src/screens/Settings/stacks/timer/styles.tsx @@ -0,0 +1,21 @@ +import useDefaultStyles from "@/components/Colors"; +import { ColorScheme } from "@/store/settings/types"; +import { StyleSheet } from "react-native"; + +export function generateTimerStyles() { + const styles = useDefaultStyles(); + + return StyleSheet.create({ + ...styles, + timer: { + display: 'flex', + flexDirection: 'row' + }, + timeInput: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 3 + } + }) +} \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts index e70428e..c5aa4fa 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -62,7 +62,7 @@ const persistConfig: PersistConfig> = { settings: { ...state.settings, enableSleepTimer: false, - sleepTime: 60, + sleepTime: 0, remainingSleepTime: 0 } }; From 85383f2447e072a27fa993dacad7053cc36df62a Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Fri, 21 Jul 2023 15:54:57 +0300 Subject: [PATCH 03/18] update pause playback when timer completes --- package-lock.json | 83 ++++++++++++------ package.json | 1 + src/screens/Settings/index.tsx | 12 +-- src/screens/Settings/stacks/timer/Timer.tsx | 91 +++++++++++++------- src/screens/Settings/stacks/timer/styles.tsx | 12 +++ src/store/index.ts | 3 +- src/store/settings/actions.ts | 1 + src/store/settings/index.ts | 8 +- src/utility/PlaybackService.ts | 30 ++++++- 9 files changed, 175 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16c944c..8b219fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-community/blur": "^4.3.0", + "@react-native-community/checkbox": "^0.5.13", "@react-native-community/netinfo": "^9.3.6", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/elements": "^1.3.17", @@ -2677,6 +2678,21 @@ "react-native": "*" } }, + "node_modules/@react-native-community/checkbox": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.16.tgz", + "integrity": "sha512-j4fmWe77EAayGnKJ52BljlN8apLT3xjxG/pJOA6HZ4ew63FiXmnY7VtxTzmvDKgSPrETdQc2lmx5mdXTAufJnw==", + "peerDependencies": { + "react": "*", + "react-native": ">= 0.62", + "react-native-windows": ">=0.62" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "node_modules/@react-native-community/cli": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.6.tgz", @@ -4409,23 +4425,15 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "node_modules/@types/react": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", - "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", + "version": "18.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz", + "integrity": "sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==", "dependencies": { "@types/prop-types": "*", + "@types/scheduler": "*", "csstype": "^3.0.2" } }, - "node_modules/@types/react-native": { - "version": "0.70.6", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.6.tgz", - "integrity": "sha512-ynQ2jj0km9d7dbnyKqVdQ6Nti7VQ8SLTA/KKkkS5+FnvGyvij2AOo1/xnkBR/jnSNXuzrvGVzw2n0VWfppmfKw==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -4444,6 +4452,11 @@ "redux": "^4.0.0" } }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "node_modules/@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -4477,6 +4490,15 @@ "@types/styled-components": "*" } }, + "node_modules/@types/styled-components-react-native/node_modules/@types/react-native": { + "version": "0.70.14", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.14.tgz", + "integrity": "sha512-Kwc+BYBrnDqvacNxKp1UtcZJnJJnTih2NYmi/ieAKlHdxEPN6sYMwmIwgHdoLHggvml6bf3DYRaH2jt+gzaLjw==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -18163,6 +18185,11 @@ "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.3.0.tgz", "integrity": "sha512-d6phh39kKcbZ4IluDftiVWqfeFOgjl1AbQWzN47x+hLKQ5GvQJ6QhRvgAuDZ+xbJksrbXgNpMjVYkjsbcVehxg==" }, + "@react-native-community/checkbox": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.16.tgz", + "integrity": "sha512-j4fmWe77EAayGnKJ52BljlN8apLT3xjxG/pJOA6HZ4ew63FiXmnY7VtxTzmvDKgSPrETdQc2lmx5mdXTAufJnw==" + }, "@react-native-community/cli": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.6.tgz", @@ -19446,23 +19473,15 @@ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/react": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz", - "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==", + "version": "18.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz", + "integrity": "sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==", "requires": { "@types/prop-types": "*", + "@types/scheduler": "*", "csstype": "^3.0.2" } }, - "@types/react-native": { - "version": "0.70.6", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.6.tgz", - "integrity": "sha512-ynQ2jj0km9d7dbnyKqVdQ6Nti7VQ8SLTA/KKkkS5+FnvGyvij2AOo1/xnkBR/jnSNXuzrvGVzw2n0VWfppmfKw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, "@types/react-test-renderer": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz", @@ -19481,6 +19500,11 @@ "redux": "^4.0.0" } }, + "@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "@types/semver": { "version": "7.3.13", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", @@ -19512,6 +19536,17 @@ "@types/react": "*", "@types/react-native": "^0.70", "@types/styled-components": "*" + }, + "dependencies": { + "@types/react-native": { + "version": "0.70.14", + "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.14.tgz", + "integrity": "sha512-Kwc+BYBrnDqvacNxKp1UtcZJnJJnTih2NYmi/ieAKlHdxEPN6sYMwmIwgHdoLHggvml6bf3DYRaH2jt+gzaLjw==", + "dev": true, + "requires": { + "@types/react": "*" + } + } } }, "@types/use-sync-external-store": { diff --git a/package.json b/package.json index bcbc2a5..9a35c6f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-community/blur": "^4.3.0", + "@react-native-community/checkbox": "^0.5.13", "@react-native-community/netinfo": "^9.3.6", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/elements": "^1.3.17", diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 3782895..2d5fa28 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -33,18 +33,18 @@ export function SettingsList() { const { sleepTime } = useTypedSelector(state => state.settings); const getTime = () => { + let hours = 0; + let minutes = 0; if (!Number.isNaN(sleepTime)) { - const hours = Math.round(sleepTime / 3600); + hours = Math.round(sleepTime / 3600); const timeRemaining = sleepTime % 3600; - let minutes = 0; - if (timeRemaining > 60) { + if (timeRemaining >= 60) { minutes = Math.round(timeRemaining / 60); } - return `${hours} hrs ${minutes} min`; - } else { - return 0; } + + return `${hours} hrs ${minutes} min`; } return ( diff --git a/src/screens/Settings/stacks/timer/Timer.tsx b/src/screens/Settings/stacks/timer/Timer.tsx index b02bcfc..acf14e1 100644 --- a/src/screens/Settings/stacks/timer/Timer.tsx +++ b/src/screens/Settings/stacks/timer/Timer.tsx @@ -4,7 +4,7 @@ import { Text } from '@/components/Typography'; import { InputContainer } from '../../components/Input'; import Input from '@/components/Input'; import useDefaultStyles from '@/components/Colors'; -import { setSleepTime } from '@/store/settings/actions'; +import { setEnabledSleeper, setSleepTime } from '@/store/settings/actions'; import { useNavigation } from '@react-navigation/native'; import { useDispatch } from 'react-redux'; import { ScrollView, ToastAndroid, View } from 'react-native'; @@ -16,12 +16,16 @@ import AlbumIcon from '@/assets/icons/collection.svg'; import TrackIcon from '@/assets/icons/note.svg'; import SelectDropdown from 'react-native-select-dropdown'; import { time } from 'console'; +import CheckBox from '@react-native-community/checkbox'; +import TrackPlayer from 'react-native-track-player'; function Timer() { const { sleepTime } = useTypedSelector(state => state.settings); + const enabledSleeper = useTypedSelector((state) => state.settings.enabledSleeper); const [minutes, setMinutes] = useState(); const [hours, setHours] = useState(); + const [enableSleeper, setEnableSleeper] = useState(enabledSleeper); const timerStyles = generateTimerStyles(); @@ -37,51 +41,72 @@ function Timer() { }, [navigation, dispatch]); const getTime = () => { + let hours = 0; + let minutes = 0; if (!Number.isNaN(sleepTime)) { - const hours = Math.round(sleepTime / 3600); + hours = Math.round(sleepTime / 3600); const timeRemaining = sleepTime % 3600; - let minutes = 0; - if (timeRemaining > 60) { + if (timeRemaining >= 60) { minutes = Math.round(timeRemaining / 60); } - return `${hours} hrs ${minutes} min`; - } else { - return 0; } + + return `${hours} hrs ${minutes} min`; } + const handleEnabledSleeper = useCallback((value: boolean) => { + dispatch(setEnabledSleeper(value)); + setEnableSleeper(value); + + // If value is true sleeper has been enabled, pause then play tack + // to trigger play state and start sleeper timer + if (value) { + TrackPlayer.pause() + TrackPlayer.play() + } + }, [dispatch]); + return ( Set Sleep Timer. Time Set Previously: {getTime()} - - - H: - { - setSleeper(parseInt(value), 'Hours'); - }} - /> - - - M: - { - setSleeper(parseInt(value), 'Minutes'); - }} - /> - + + handleEnabledSleeper(value)} + /> + Enable Sleeper + + + + + H: + { + setSleeper(parseInt(value), 'Hours'); + }} + /> + + + M: + { + setSleeper(parseInt(value), 'Minutes'); + }} + /> + + + Set this to automatically stop the audio when time runs out. - Set this to automatically stop the audio when time runs out. ); diff --git a/src/screens/Settings/stacks/timer/styles.tsx b/src/screens/Settings/stacks/timer/styles.tsx index 78f0c44..d1f93b7 100644 --- a/src/screens/Settings/stacks/timer/styles.tsx +++ b/src/screens/Settings/stacks/timer/styles.tsx @@ -16,6 +16,18 @@ export function generateTimerStyles() { flexDirection: 'row', alignItems: 'center', gap: 3 + }, + checkbox: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center' + }, + timerSetting: { + marginStart: 30 + }, + timerSettingsDisabled: { + color: '#cbcbcb', + marginStart: 30 } }) } \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts index c5aa4fa..71e8134 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -63,7 +63,8 @@ const persistConfig: PersistConfig> = { ...state.settings, enableSleepTimer: false, sleepTime: 0, - remainingSleepTime: 0 + remainingSleepTime: 0, + enabledSleeper: false } }; }, diff --git a/src/store/settings/actions.ts b/src/store/settings/actions.ts index 029ef86..97ea6a1 100644 --- a/src/store/settings/actions.ts +++ b/src/store/settings/actions.ts @@ -10,3 +10,4 @@ export const setColorScheme = createAction('SET_COLOR_SCHEME'); export const setSleepTime = createAction('SET_SLEEP_TIME'); export const setEnableSleepTimer = createAction('SET_ENABLE_SLEEP_TIMER'); export const setRemainingSleepTime = createAction('SET_REMAINING_SLEEP_TIME'); +export const setEnabledSleeper = createAction('SET_ENABLE_SLEEPER'); diff --git a/src/store/settings/index.ts b/src/store/settings/index.ts index 6e0dd32..7248d44 100644 --- a/src/store/settings/index.ts +++ b/src/store/settings/index.ts @@ -1,5 +1,5 @@ import { createReducer } from '@reduxjs/toolkit'; -import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting, setColorScheme, setSleepTime, setEnableSleepTimer, setRemainingSleepTime } from './actions'; +import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus, setEnablePlaybackReporting, setColorScheme, setSleepTime, setEnableSleepTimer, setRemainingSleepTime, setEnabledSleeper } from './actions'; import { ColorScheme } from './types'; interface State { @@ -15,6 +15,7 @@ interface State { enablePlaybackReporting: boolean; colorScheme: ColorScheme; sleepTime: number; + enabledSleeper: boolean } const initialState: State = { @@ -24,6 +25,7 @@ const initialState: State = { enablePlaybackReporting: true, colorScheme: ColorScheme.System, sleepTime: 60, + enabledSleeper: false, }; const settings = createReducer(initialState, builder => { @@ -63,6 +65,10 @@ const settings = createReducer(initialState, builder => { ...state, remainingSleepTime: action.payload, })); + builder.addCase(setEnabledSleeper, (state, action) => ({ + ...state, + enabledSleeper: action.payload, + })); }); export default settings; \ No newline at end of file diff --git a/src/utility/PlaybackService.ts b/src/utility/PlaybackService.ts index 4ae9dc5..a170543 100644 --- a/src/utility/PlaybackService.ts +++ b/src/utility/PlaybackService.ts @@ -8,10 +8,15 @@ */ import TrackPlayer, { Event, State } from 'react-native-track-player'; -import store from '@/store'; +import store, { useTypedSelector } from '@/store'; import { sendPlaybackEvent } from './JellyfinApi'; +import { useDispatch } from 'react-redux'; +import { setSleepTime } from '@/store/settings/actions'; +import internal from 'stream'; export default async function() { + let interval = setInterval(() => {}, 0); + TrackPlayer.addEventListener(Event.RemotePlay, () => { TrackPlayer.play(); }); @@ -59,6 +64,11 @@ export default async function() { if (settings.enablePlaybackReporting) { sendPlaybackEvent('/Sessions/Playing/Progress', settings.jellyfin); } + + // regularly check if sleeper is enabled, then disable the timer + if (!settings.enabledSleeper) { + clearInterval(interval); + } }); TrackPlayer.addEventListener(Event.PlaybackState, (event) => { @@ -72,6 +82,24 @@ export default async function() { sendPlaybackEvent('/Sessions/Playing/Stopped', settings.jellyfin); } } + + // Handle is playback state is playing + if (event.state === State.Playing) { + const settings = store.getState().settings; + + // Start timer is sleeper is enabled + if (settings.enabledSleeper) { + let time = settings.sleepTime; + interval = setInterval(() => { + if (time > 0) { + time -= 1; + } else { + TrackPlayer.pause(); + clearInterval(interval); + } + }, 1000); + } + } }); } \ No newline at end of file From 51321ccc7ec3b36a38ef162e8f262d0c60f89bf4 Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Thu, 27 Jul 2023 18:04:24 +0300 Subject: [PATCH 04/18] use switch instead of a checkbox --- package.json | 2 -- src/localisation/lang/en/locale.json | 3 ++- src/localisation/types.ts | 1 + src/screens/Settings/index.tsx | 5 +---- src/screens/Settings/stacks/timer/Timer.tsx | 17 ++++++++++------- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 9a35c6f..2e0eed1 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-community/blur": "^4.3.0", - "@react-native-community/checkbox": "^0.5.13", "@react-native-community/netinfo": "^9.3.6", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/elements": "^1.3.17", @@ -47,7 +46,6 @@ "react-native-reanimated": "^3.6.2", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", - "react-native-select-dropdown": "^3.3.4", "react-native-shadow-2": "^7.0.6", "react-native-skia": "^0.0.1", "react-native-svg": "^13.5.0", diff --git a/src/localisation/lang/en/locale.json b/src/localisation/lang/en/locale.json index 788c9fa..b08ca2b 100644 --- a/src/localisation/lang/en/locale.json +++ b/src/localisation/lang/en/locale.json @@ -71,5 +71,6 @@ "color-scheme-light": "Light Mode", "color-scheme-dark": "Dark Mode", "artists": "Artists", - "privacy-policy": "Privacy Policy" + "privacy-policy": "Privacy Policy", + "enable-sleeper": "Enable Sleeper" } diff --git a/src/localisation/types.ts b/src/localisation/types.ts index 5f85594..ec7dcdd 100644 --- a/src/localisation/types.ts +++ b/src/localisation/types.ts @@ -70,3 +70,4 @@ export type LocaleKeys = 'play-next' | 'artists' | 'privacy-policy' | 'timer' +| 'timer' diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 2d5fa28..d2c87ed 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -50,10 +50,7 @@ export function SettingsList() { return ( {t('jellyfin-library')} - - {t('timer')} - {`Set Time: ${getTime()}s`} - + {t('timer')} {t('setting-cache')} {t('error-reporting')} {t('playback-reporting')} diff --git a/src/screens/Settings/stacks/timer/Timer.tsx b/src/screens/Settings/stacks/timer/Timer.tsx index acf14e1..de33a03 100644 --- a/src/screens/Settings/stacks/timer/Timer.tsx +++ b/src/screens/Settings/stacks/timer/Timer.tsx @@ -18,6 +18,9 @@ import SelectDropdown from 'react-native-select-dropdown'; import { time } from 'console'; import CheckBox from '@react-native-community/checkbox'; import TrackPlayer from 'react-native-track-player'; +import { Switch } from 'react-native-gesture-handler'; +import { SwitchContainer, SwitchLabel } from '../../components/Switch'; +import { t } from '@/localisation'; function Timer() { const { sleepTime } = useTypedSelector(state => state.settings); @@ -53,7 +56,7 @@ function Timer() { } return `${hours} hrs ${minutes} min`; - } + }; const handleEnabledSleeper = useCallback((value: boolean) => { dispatch(setEnabledSleeper(value)); @@ -62,8 +65,8 @@ function Timer() { // If value is true sleeper has been enabled, pause then play tack // to trigger play state and start sleeper timer if (value) { - TrackPlayer.pause() - TrackPlayer.play() + TrackPlayer.pause(); + TrackPlayer.play(); } }, [dispatch]); @@ -71,13 +74,13 @@ function Timer() { Set Sleep Timer. Time Set Previously: {getTime()} - - + handleEnabledSleeper(value)} /> - Enable Sleeper - + {t('enable-sleeper')} + From 439235e6f8d3cd8da8f9bf24ff470398ded4d403 Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Thu, 27 Jul 2023 18:31:06 +0300 Subject: [PATCH 05/18] update: remove arrow function and list button child component text --- src/localisation/lang/en/locale.json | 4 +--- src/localisation/lang/nl/locale.json | 1 - src/localisation/types.ts | 2 -- src/screens/Settings/index.tsx | 25 +++------------------ src/screens/Settings/stacks/timer/Timer.tsx | 2 +- 5 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/localisation/lang/en/locale.json b/src/localisation/lang/en/locale.json index b08ca2b..bbf005b 100644 --- a/src/localisation/lang/en/locale.json +++ b/src/localisation/lang/en/locale.json @@ -41,7 +41,6 @@ "more-info": "More Info", "track": "Track", "playlists": "Playlists", - "timer": "Set Sleep Timer", "playlist": "Playlist", "play-playlist": "Play Playlist", "shuffle-album": "Shuffle Album", @@ -71,6 +70,5 @@ "color-scheme-light": "Light Mode", "color-scheme-dark": "Dark Mode", "artists": "Artists", - "privacy-policy": "Privacy Policy", - "enable-sleeper": "Enable Sleeper" + "privacy-policy": "Privacy Policy" } diff --git a/src/localisation/lang/nl/locale.json b/src/localisation/lang/nl/locale.json index 67cef3e..485d822 100644 --- a/src/localisation/lang/nl/locale.json +++ b/src/localisation/lang/nl/locale.json @@ -41,7 +41,6 @@ "more-info": "Meer informatie", "track": "Track", "playlists": "Playlists", - "timer": "Set Sleep Timer", "playlist": "Playlist", "play-playlist": "Speel Playlist", "shuffle-album": "Shuffle Album", diff --git a/src/localisation/types.ts b/src/localisation/types.ts index ec7dcdd..34e431d 100644 --- a/src/localisation/types.ts +++ b/src/localisation/types.ts @@ -69,5 +69,3 @@ export type LocaleKeys = 'play-next' | 'color-scheme-dark' | 'artists' | 'privacy-policy' -| 'timer' -| 'timer' diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index d2c87ed..0385548 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet } from 'react-native'; import { t } from '@/localisation'; import { createStackNavigator } from '@react-navigation/stack'; import { useNavigation } from '@react-navigation/native'; @@ -17,8 +17,6 @@ import PlaybackReporting from './stacks/PlaybackReporting'; import { SafeScrollView } from '@/components/SafeNavigatorView'; import PrivacyPolicy from './components/PrivacyPolicy'; import Timer from './stacks/timer/Timer'; -import { Paragraph, Text } from '@/components/Typography'; -import { useTypedSelector } from '@/store'; export function SettingsList() { const navigation = useNavigation(); @@ -30,27 +28,10 @@ export function SettingsList() { const handlePrivacyPolicyClick = useCallback(() => { navigation.navigate('PrivacyPolicy'); }, [navigation]); const handleTimerClick = useCallback(() => { navigation.navigate('Timer'); }, [navigation]); - const { sleepTime } = useTypedSelector(state => state.settings); - - const getTime = () => { - let hours = 0; - let minutes = 0; - if (!Number.isNaN(sleepTime)) { - hours = Math.round(sleepTime / 3600); - const timeRemaining = sleepTime % 3600; - - if (timeRemaining >= 60) { - minutes = Math.round(timeRemaining / 60); - } - } - - return `${hours} hrs ${minutes} min`; - } - return ( {t('jellyfin-library')} - {t('timer')} + Set Sleep Timer {t('setting-cache')} {t('error-reporting')} {t('playback-reporting')} @@ -74,7 +55,7 @@ export default function Settings() { }}> - + diff --git a/src/screens/Settings/stacks/timer/Timer.tsx b/src/screens/Settings/stacks/timer/Timer.tsx index de33a03..8696f52 100644 --- a/src/screens/Settings/stacks/timer/Timer.tsx +++ b/src/screens/Settings/stacks/timer/Timer.tsx @@ -79,7 +79,7 @@ function Timer() { value={enableSleeper} onValueChange={(value) => handleEnabledSleeper(value)} /> - {t('enable-sleeper')} + Enable Sleeper From 3d481a8f180d9980df2004e65dd7bcecbaaf3302 Mon Sep 17 00:00:00 2001 From: Benard Mathu Date: Mon, 31 Jul 2023 00:32:05 +0300 Subject: [PATCH 06/18] Update with requirements: set date to trigger --- package-lock.json | 70 +++++----- package.json | 2 + src/screens/Settings/index.tsx | 2 +- src/screens/Settings/stacks/timer/Timer.tsx | 132 +++++++------------ src/screens/Settings/stacks/timer/styles.tsx | 19 ++- src/store/index.ts | 6 +- src/store/settings/actions.ts | 5 +- src/store/settings/index.ts | 22 ++-- src/utility/PlaybackService.ts | 39 ++---- 9 files changed, 124 insertions(+), 173 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b219fa..6276c59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-community/blur": "^4.3.0", - "@react-native-community/checkbox": "^0.5.13", + "@react-native-community/datetimepicker": "^7.4.1", "@react-native-community/netinfo": "^9.3.6", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/elements": "^1.3.17", @@ -40,10 +40,10 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.9.0", "react-native-localize": "^2.2.4", + "react-native-modal-datetime-picker": "^17.0.0", "react-native-reanimated": "^3.6.2", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", - "react-native-select-dropdown": "^3.3.4", "react-native-shadow-2": "^7.0.6", "react-native-skia": "^0.0.1", "react-native-svg": "^13.5.0", @@ -2678,21 +2678,6 @@ "react-native": "*" } }, - "node_modules/@react-native-community/checkbox": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.16.tgz", - "integrity": "sha512-j4fmWe77EAayGnKJ52BljlN8apLT3xjxG/pJOA6HZ4ew63FiXmnY7VtxTzmvDKgSPrETdQc2lmx5mdXTAufJnw==", - "peerDependencies": { - "react": "*", - "react-native": ">= 0.62", - "react-native-windows": ">=0.62" - }, - "peerDependenciesMeta": { - "react-native-windows": { - "optional": true - } - } - }, "node_modules/@react-native-community/cli": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.6.tgz", @@ -2981,6 +2966,14 @@ "semver": "bin/semver.js" } }, + "node_modules/@react-native-community/datetimepicker": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-7.4.1.tgz", + "integrity": "sha512-S7KdiWt0VgL93vy8sAlxPtyq8yNTRCNvoVJPkPlKzwuDY1Q5f+E0rsnNvfP0Y/UMhXAUnUo/THGR2qfrsJ9vNg==", + "dependencies": { + "invariant": "^2.2.4" + } + }, "node_modules/@react-native-community/eslint-config": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.2.0.tgz", @@ -13737,6 +13730,18 @@ } } }, + "node_modules/react-native-modal-datetime-picker": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-17.0.0.tgz", + "integrity": "sha512-/NFZMlugmd4WnKfNHjWQ5vz7PWfL2ECg8+hH0i661EqunT3+Oulp8a/0BCjNfCy2YEHargeII4T0ztLEe5MsfA==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@react-native-community/datetimepicker": ">=6.7.0", + "react-native": ">=0.65.0" + } + }, "node_modules/react-native-reanimated": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.2.tgz", @@ -13785,11 +13790,6 @@ "react-native": "*" } }, - "node_modules/react-native-select-dropdown": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/react-native-select-dropdown/-/react-native-select-dropdown-3.3.4.tgz", - "integrity": "sha512-Ld6BGGgCnbiv7uORAP+KnvDQiqeuqdlasKk9woJH9XtFMD44rwjKwelGzsDxFUM9hIAwZdac4UAFmOmXRaMeRg==" - }, "node_modules/react-native-shadow-2": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/react-native-shadow-2/-/react-native-shadow-2-7.0.6.tgz", @@ -18185,11 +18185,6 @@ "resolved": "https://registry.npmjs.org/@react-native-community/blur/-/blur-4.3.0.tgz", "integrity": "sha512-d6phh39kKcbZ4IluDftiVWqfeFOgjl1AbQWzN47x+hLKQ5GvQJ6QhRvgAuDZ+xbJksrbXgNpMjVYkjsbcVehxg==" }, - "@react-native-community/checkbox": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@react-native-community/checkbox/-/checkbox-0.5.16.tgz", - "integrity": "sha512-j4fmWe77EAayGnKJ52BljlN8apLT3xjxG/pJOA6HZ4ew63FiXmnY7VtxTzmvDKgSPrETdQc2lmx5mdXTAufJnw==" - }, "@react-native-community/cli": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.6.tgz", @@ -18438,6 +18433,14 @@ "joi": "^17.2.1" } }, + "@react-native-community/datetimepicker": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-7.4.1.tgz", + "integrity": "sha512-S7KdiWt0VgL93vy8sAlxPtyq8yNTRCNvoVJPkPlKzwuDY1Q5f+E0rsnNvfP0Y/UMhXAUnUo/THGR2qfrsJ9vNg==", + "requires": { + "invariant": "^2.2.4" + } + }, "@react-native-community/eslint-config": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@react-native-community/eslint-config/-/eslint-config-3.2.0.tgz", @@ -26673,6 +26676,14 @@ "resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-2.2.4.tgz", "integrity": "sha512-gVmbyAEQQnBQ8vKlAQchFfIISeId3qT6Lc7LHmKF39nsYWX9KN4PHuG6Hk+7gduMI6IHKeZGKcLsOdh6wvN6cg==" }, + "react-native-modal-datetime-picker": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-17.0.0.tgz", + "integrity": "sha512-/NFZMlugmd4WnKfNHjWQ5vz7PWfL2ECg8+hH0i661EqunT3+Oulp8a/0BCjNfCy2YEHargeII4T0ztLEe5MsfA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-native-reanimated": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.2.tgz", @@ -26705,11 +26716,6 @@ "warn-once": "^0.1.0" } }, - "react-native-select-dropdown": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/react-native-select-dropdown/-/react-native-select-dropdown-3.3.4.tgz", - "integrity": "sha512-Ld6BGGgCnbiv7uORAP+KnvDQiqeuqdlasKk9woJH9XtFMD44rwjKwelGzsDxFUM9hIAwZdac4UAFmOmXRaMeRg==" - }, "react-native-shadow-2": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/react-native-shadow-2/-/react-native-shadow-2-7.0.6.tgz", diff --git a/package.json b/package.json index 2e0eed1..be84c11 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "@react-native-async-storage/async-storage": "^1.17.11", "@react-native-community/blur": "^4.3.0", + "@react-native-community/datetimepicker": "^7.4.1", "@react-native-community/netinfo": "^9.3.6", "@react-navigation/bottom-tabs": "^6.4.0", "@react-navigation/elements": "^1.3.17", @@ -44,6 +45,7 @@ "react-native-gesture-handler": "^2.9.0", "react-native-localize": "^2.2.4", "react-native-reanimated": "^3.6.2", + "react-native-modal-datetime-picker": "^17.0.0", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", "react-native-shadow-2": "^7.0.6", diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index 0385548..ac789b5 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -55,7 +55,7 @@ export default function Settings() { }}> - + diff --git a/src/screens/Settings/stacks/timer/Timer.tsx b/src/screens/Settings/stacks/timer/Timer.tsx index 8696f52..12326ec 100644 --- a/src/screens/Settings/stacks/timer/Timer.tsx +++ b/src/screens/Settings/stacks/timer/Timer.tsx @@ -2,114 +2,72 @@ import React, { useCallback, useState } from 'react'; import Container from '../../components/Container'; import { Text } from '@/components/Typography'; import { InputContainer } from '../../components/Input'; -import Input from '@/components/Input'; -import useDefaultStyles from '@/components/Colors'; -import { setEnabledSleeper, setSleepTime } from '@/store/settings/actions'; -import { useNavigation } from '@react-navigation/native'; -import { useDispatch } from 'react-redux'; -import { ScrollView, ToastAndroid, View } from 'react-native'; -import { generateTimerStyles } from './styles'; -import { useTypedSelector } from '@/store'; -import SelectableFilter from '@/screens/Search/stacks/Search/components/SelectableFilter'; -import MicrophoneIcon from '@/assets/icons/microphone.svg'; -import AlbumIcon from '@/assets/icons/collection.svg'; -import TrackIcon from '@/assets/icons/note.svg'; -import SelectDropdown from 'react-native-select-dropdown'; -import { time } from 'console'; -import CheckBox from '@react-native-community/checkbox'; -import TrackPlayer from 'react-native-track-player'; +import { useTimeStyles } from './styles'; import { Switch } from 'react-native-gesture-handler'; import { SwitchContainer, SwitchLabel } from '../../components/Switch'; -import { t } from '@/localisation'; +import Button from '@/components/Button'; +import { View } from 'react-native'; +import DateTimePickerModal from 'react-native-modal-datetime-picker'; +import { useTypedSelector } from '@/store'; +import { useDispatch } from 'react-redux'; +import { setDateTime, setEnableSleepTime } from '@/store/settings/actions'; function Timer() { - const { sleepTime } = useTypedSelector(state => state.settings); - const enabledSleeper = useTypedSelector((state) => state.settings.enabledSleeper); + const [show, setShow] = useState(false); + const { dateTime } = useTypedSelector(state => state.settings); + const [date, setDate] = useState(dateTime === undefined ? 'Set Time' : dateTime.toString()); - const [minutes, setMinutes] = useState(); - const [hours, setHours] = useState(); - const [enableSleeper, setEnableSleeper] = useState(enabledSleeper); + const timerStyles = useTimeStyles(); + const { enableSleepTime } = useTypedSelector(state => state.settings); - const timerStyles = generateTimerStyles(); - - const navigation = useNavigation(); const dispatch = useDispatch(); - const setSleeper = useCallback((value: number, timeType: string) => { - if (timeType == 'Hours' && value > 0) { - dispatch(setSleepTime(value * 3600)); - } else if (timeType == 'Minutes' && value > 0) { - dispatch(setSleepTime(value * 60)); - } - }, [navigation, dispatch]); - - const getTime = () => { - let hours = 0; - let minutes = 0; - if (!Number.isNaN(sleepTime)) { - hours = Math.round(sleepTime / 3600); - const timeRemaining = sleepTime % 3600; - - if (timeRemaining >= 60) { - minutes = Math.round(timeRemaining / 60); - } - } - - return `${hours} hrs ${minutes} min`; + const handleEnabledSleeper = useCallback((value: boolean) => { + dispatch(setEnableSleepTime(value)); + }, [dispatch]); + + const handleConfirm = (date: Date) => { + setShow(false); + setDate(date.toString()); + dispatch(setDateTime(date)); }; - const handleEnabledSleeper = useCallback((value: boolean) => { - dispatch(setEnabledSleeper(value)); - setEnableSleeper(value); - - // If value is true sleeper has been enabled, pause then play tack - // to trigger play state and start sleeper timer - if (value) { - TrackPlayer.pause(); - TrackPlayer.play(); - } - }, [dispatch]); + const showDateTimePicker = () => { + setShow(!show); + }; + const handleCancelDatePicker = () => { + setShow(false); + }; + return ( - Set Sleep Timer. Time Set Previously: {getTime()} + {'Set Sleep Time.'} handleEnabledSleeper(value)} /> - Enable Sleeper + {'Enable Timer'} - - - - H: - { - setSleeper(parseInt(value), 'Hours'); - }} - /> - - - M: - { - setSleeper(parseInt(value), 'Minutes'); - }} - /> - + + + {'Set Time'} +