Add buttons for deleting downloaded tracks from playlist or album
This commit is contained in:
@@ -20,6 +20,10 @@ const BaseButton = styled.Pressable`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
|
||||
${(props) => props.disabled && css`
|
||||
opacity: 0.25;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ButtonText = styled.Text<{ active?: boolean }>`
|
||||
@@ -32,7 +36,7 @@ const ButtonText = styled.Text<{ active?: boolean }>`
|
||||
`;
|
||||
|
||||
const Button = React.forwardRef<View, ButtonProps>(function Button(props, ref) {
|
||||
const { icon: Icon, title, ...rest } = props;
|
||||
const { icon: Icon, title, disabled, ...rest } = props;
|
||||
const defaultStyles = useDefaultStyles();
|
||||
const [isPressed, setPressed] = useState(false);
|
||||
const handlePressIn = useCallback(() => setPressed(true), []);
|
||||
@@ -41,6 +45,7 @@ const Button = React.forwardRef<View, ButtonProps>(function Button(props, ref) {
|
||||
return (
|
||||
<BaseButton
|
||||
{...rest}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
|
||||
@@ -51,5 +51,7 @@
|
||||
"download-playlist": "Download Playlist",
|
||||
"no-downloads": "You have not yet downloaded any tracks",
|
||||
"delete-all-tracks": "Delete All Tracks",
|
||||
"delete-album": "Delete Album",
|
||||
"delete-playlist": "Delete Playlist",
|
||||
"total-download-size": "Total Download Size"
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export type LocaleKeys = 'play-next'
|
||||
| 'download-track'
|
||||
| 'download-album'
|
||||
| 'download-playlist'
|
||||
| 'delete-all-tracks'
|
||||
| 'delete-album'
|
||||
| 'delete-playlist'
|
||||
| 'total-download-size'
|
||||
| 'no-downloads'
|
||||
@@ -37,7 +37,8 @@ const Album: React.FC = () => {
|
||||
refresh={refresh}
|
||||
playButtonText={t('play-album')}
|
||||
shuffleButtonText={t('shuffle-album')}
|
||||
downloadText={t('download-album')}
|
||||
downloadButtonText={t('download-album')}
|
||||
deleteButtonText={t('delete-album')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,8 @@ const Playlist: React.FC = () => {
|
||||
listNumberingStyle='index'
|
||||
playButtonText={t('play-playlist')}
|
||||
shuffleButtonText={t('shuffle-playlist')}
|
||||
downloadText={t('download-playlist')}
|
||||
downloadButtonText={t('download-playlist')}
|
||||
deleteButtonText={t('delete-playlist')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,8 +19,10 @@ import { MusicNavigationProp } from 'screens/Music/types';
|
||||
import DownloadIcon from 'components/DownloadIcon';
|
||||
import Button from 'components/Button';
|
||||
import CloudDownArrow from 'assets/cloud-down-arrow.svg';
|
||||
import Trash from 'assets/trash.svg';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { downloadTrack } from 'store/downloads/actions';
|
||||
import { downloadTrack, removeDownloadedTrack } from 'store/downloads/actions';
|
||||
import { selectDownloadedTracks } from 'store/downloads/selectors';
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
|
||||
@@ -68,7 +70,8 @@ interface TrackListViewProps {
|
||||
refresh: () => void;
|
||||
playButtonText: string;
|
||||
shuffleButtonText: string;
|
||||
downloadText: string;
|
||||
downloadButtonText: string;
|
||||
deleteButtonText: string;
|
||||
listNumberingStyle?: 'album' | 'index';
|
||||
}
|
||||
|
||||
@@ -80,7 +83,8 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
refresh,
|
||||
playButtonText,
|
||||
shuffleButtonText,
|
||||
downloadText,
|
||||
downloadButtonText,
|
||||
deleteButtonText,
|
||||
listNumberingStyle = 'album',
|
||||
}) => {
|
||||
const defaultStyles = useDefaultStyles();
|
||||
@@ -88,6 +92,7 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
// Retrieve state
|
||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
|
||||
const downloadedTracks = useTypedSelector(selectDownloadedTracks(trackIds));
|
||||
|
||||
// Retrieve helpers
|
||||
const getImage = useGetImage();
|
||||
@@ -110,6 +115,9 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
const downloadAllTracks = useCallback(() => {
|
||||
trackIds.forEach((trackId) => dispatch(downloadTrack(trackId)));
|
||||
}, [dispatch, trackIds]);
|
||||
const deleteAllTracks = useCallback(() => {
|
||||
downloadedTracks.forEach((trackId) => dispatch(removeDownloadedTrack(trackId)));
|
||||
}, [dispatch, downloadedTracks]);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -162,7 +170,20 @@ const TrackListView: React.FC<TrackListViewProps> = ({
|
||||
</TrackContainer>
|
||||
</TouchableHandler>
|
||||
)}
|
||||
<Button icon={CloudDownArrow} title={downloadText} style={{ marginTop: 12 }} onPress={downloadAllTracks} />
|
||||
<WrappableButtonRow style={{ marginTop: 24 }}>
|
||||
<WrappableButton
|
||||
icon={CloudDownArrow}
|
||||
title={downloadButtonText}
|
||||
onPress={downloadAllTracks}
|
||||
disabled={downloadedTracks.length === trackIds.length}
|
||||
/>
|
||||
<WrappableButton
|
||||
icon={Trash}
|
||||
title={deleteButtonText}
|
||||
onPress={deleteAllTracks}
|
||||
disabled={downloadedTracks.length === 0}
|
||||
/>
|
||||
</WrappableButtonRow>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
|
||||
13
src/store/downloads/selectors.ts
Normal file
13
src/store/downloads/selectors.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createSelector, EntityId } from '@reduxjs/toolkit';
|
||||
import { intersection } from 'lodash';
|
||||
import { AppState } from 'store';
|
||||
|
||||
export const selectDownloadedTracks = (trackIds: EntityId[]) => (
|
||||
createSelector(
|
||||
(state: AppState) => state.downloads,
|
||||
({ entities, ids }) => {
|
||||
return intersection(trackIds, ids)
|
||||
.filter((id) => entities[id]?.isComplete);
|
||||
}
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user