Skip to content

Commit

Permalink
Completed listening history functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Rajit Khatri authored and Rajit Khatri committed Nov 5, 2024
1 parent 1e38147 commit d05c894
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 14 deletions.
80 changes: 80 additions & 0 deletions src/components/ListeningHistoryModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import styles from "../css/app.module.scss";
import React from "react";
import type { HistoricalMetrics } from "../types/enhancify";
import getRecentlyPlayedTracksMetrics from "./../services/recentlyPlayedService"
import SongMetric from "./SongMetric";

class ListeningHistoryModal extends React.Component <{setModalIsOpen: (value: boolean) => void}, {metricsValues: HistoricalMetrics | {}}> {
state = {
metricsValues: {
acousticness: {
average: 0,
count: 0
},
danceability: {
average: 0,
count: 0
},
energy: {
average: 0,
count: 0
},
instrumentalness: {
average: 0,
count: 0
},
liveness: {
average: 0,
count: 0
},
speechiness: {
average: 0,
count: 0
},
valence: {
average: 0,
count: 0
},
}
}

async componentDidMount(): Promise<void> {
let newState = await getRecentlyPlayedTracksMetrics();
this.setState({
metricsValues: newState,
});
}

render() {
return (
<div className={styles.settingsModalContainer} style={{paddingBottom: "20px", paddingLeft: "40px"}}>
<div className={styles.modalHeaderContainer}>
<div className={styles.recommendationsLabel} style={{marginLeft: "20px",
marginBottom: "0px",
marginTop: "10px",
}}>
{"Listening History"}
</div>
<img className={styles.playIcon} style={{marginLeft: "auto"}}
src={"https://img.icons8.com/?size=100&id=6483&format=png&color=FFFFFF"}
onClick={() => this.props.setModalIsOpen(false)}/>
</div>
<div className={styles.statsBlock} style={{marginBottom: "40px"}}>
{Object.keys(this.state.metricsValues).map((metric: string) => {
if (this.state.metricsValues[metric as keyof HistoricalMetrics].count) {
return <SongMetric title={metric}
floatValue={this.state.metricsValues[metric as keyof HistoricalMetrics].average.toString()}
label={""}
progressBar={true}
selectMetric={(metric: string, value: string) => {}}
></SongMetric>;
} else {
return <></>;
}
})}
</div>
</div>);
}
}

export default ListeningHistoryModal;
37 changes: 35 additions & 2 deletions src/components/NowPlaying.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SelectedMetrics, SongMetricData } from "../types/enhancify";
import { allMetrics, getSongMetrics } from "../services/enhancifyInternalService";
import RecommendationsModal from "./RecommendationsModal";
import SettingsModal from "./SettingsModal";
import ListeningHistoryModal from "./ListeningHistoryModal";
import Modal from 'react-modal';

class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesResponse | {},
Expand All @@ -17,6 +18,7 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
metricsToDisplay: string[],
modalIsOpen: boolean,
settingsModalIsOpen: boolean,
historyModalIsOpen: boolean,
selectedMetrics: SelectedMetrics}> {

state = {
Expand All @@ -29,7 +31,8 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
["Danceability", "Energy", "Acousticness", "Loudness", "Key", "Tempo"] :
[], // Current metric information types
modalIsOpen: false, // Whether the modal is currently open
settingsModalIsOpen: false,
settingsModalIsOpen: false,
historyModalIsOpen: false,
selectedMetrics: JSON.parse(Spicetify.LocalStorage.get("selectedMetrics") ||
"{}"), // Metrics that have been selected to be fed into the Spotify recommendations endpoint
}
Expand Down Expand Up @@ -120,6 +123,12 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
});
}

setHistoryModalIsOpen = (value: boolean) => {
this.setState({
historyModalIsOpen: value,
})
}

// Select a metric to toggle whether they should be included in the recommendations endpoint request or not
selectMetric = (metric: string, value: string) => {
let copy: SelectedMetrics = { ...this.state.selectedMetrics };
Expand Down Expand Up @@ -163,6 +172,20 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
},
}

historicalMetricsModalStyles = {
overlay: {
backgroundColor: "rgba(0, 0, 0, 0.70)",
},
content: {
position: 'absolute',
top: '43%',
left: '47%',
transform: 'translate(-50%, -50%)',
width: "70%",
height: "640px"
},
}

render() {

Spicetify.Player.addEventListener("songchange", this.setAudioFeatures);
Expand Down Expand Up @@ -248,13 +271,20 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
marginTop: "auto",
marginBottom: "auto"}} />
</div>
<div className={styles.settingsIconContainer} onClick={() => this.setSettingsModalIsOpen(true)}>
<div className={styles.settingsIconContainer} style={{marginRight: "0px"}} onClick={() => this.setSettingsModalIsOpen(true)}>
<img src={"https://img.icons8.com/?size=100&id=2969&format=png&color=FFFFFF"}
style={{width: "25px",
height: "25px",
marginTop: "auto",
marginBottom: "auto"}} />
</div>
<div className={styles.settingsIconContainer} onClick={() => this.setHistoryModalIsOpen(true)}>
<img src={"https://img.icons8.com/?size=100&id=8309&format=png&color=FFFFFF"}
style={{width: "25px",
height: "25px",
marginTop: "auto",
marginBottom: "auto"}} />
</div>
</div>
<div className={styles.statsBlock}>

Expand All @@ -280,6 +310,9 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
<SettingsModal changeRecTarget={this.changeRecTarget} toggleMetric={this.toggleMetric} recTarget={this.state.recTarget} metricsToDisplay={this.state.metricsToDisplay}
setModalIsOpen={this.setSettingsModalIsOpen}/>
</Modal>
<Modal className={styles.modal} isOpen={this.state.historyModalIsOpen} onRequestClose={() => this.setHistoryModalIsOpen(false)} style={this.historicalMetricsModalStyles}>
<ListeningHistoryModal setModalIsOpen={this.setHistoryModalIsOpen}></ListeningHistoryModal>
</Modal>
</>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/SongMetric.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class SongMetric extends React.Component<{floatValue: string,
title: string,
progressBar: boolean,
label: string,
selectMetric: (metric: string, value: string) => void
isMetricSelected: boolean},
selectMetric: (metric: string, value: string) => void
isMetricSelected?: boolean},
{}> {

render() {
Expand Down
35 changes: 25 additions & 10 deletions src/services/recentlyPlayedService.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import type { GetRecentlyPlayedTracksResponse, GetRecentlyPlayedTracksInput, AudioFeaturesResponse } from "../types/spotify-web-api";
import type { AudioFeaturesResponse } from "../types/spotify-web-api";
import getMultiTrackAudioFeatures from "./multiTrackAudioFeaturesService";
import type { HistoricalMetrics } from "../types/enhancify";

async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTracksInput) : Promise<HistoricalMetrics | {}> {
let url = "https://api.spotify.com/v1/me/player/recently-played";
async function getRecentlyPlayedTracksMetrics() : Promise<HistoricalMetrics | {}> {

// Set the API option
let apiOptions = {
limit: "50",
after: "",
};
// Get the date of the first day of the week
let today = new Date();
let day = today.getDay() || 7;
if (day != 1) {
today.setHours(-24 * (day - 1));
}
// Convert the date to UNIX format
apiOptions.after = (today.getTime() / 1000).toFixed(0);

let url = "https://api.spotify.com/v1/me/player/recently-played?";

// Extract the query parameters from the API options input
// to create a request string
let queryParams: string[] = [];
for (const [key, value] of Object.entries(apiOptions.data)) {
for (const [key, value] of Object.entries(apiOptions)) {
if (!value) {
continue;
}
Expand All @@ -25,7 +40,6 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack
headers: {
Authorization: "Bearer " + accessToken,
},

}
);

Expand All @@ -43,7 +57,6 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack
songIDs.push(item.track.id);
}
let audioFeatures = await getMultiTrackAudioFeatures(songIDs);

// Process the audio features
let relevantMetrics = ["acousticness", "danceability", "energy", "instrumentalness",
"liveness", "speechiness", "valence"];
Expand All @@ -53,9 +66,9 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack
}
for (let audioFeature of audioFeatures) {
for (let feature of Object.keys(audioFeature)) {
if (feature in relevantMetrics) {
if (relevantMetrics.includes(feature)) {
if (audioFeature[feature as keyof AudioFeaturesResponse]) {
metricsData[feature].total += parseInt(audioFeature[feature as keyof AudioFeaturesResponse]);
metricsData[feature].total += parseFloat(audioFeature[feature as keyof AudioFeaturesResponse]);
metricsData[feature].count += 1;
}
}
Expand Down Expand Up @@ -93,10 +106,12 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack
count: 0
}
};
for (let metric in Object.keys(metricsData)) {
for (let metric of Object.keys(metricsData)) {
result[metric as keyof HistoricalMetrics].average = metricsData[metric].total / metricsData[metric].count;
result[metric as keyof HistoricalMetrics].count = metricsData[metric].count;
}

return result;
}
}

export default getRecentlyPlayedTracksMetrics;

0 comments on commit d05c894

Please sign in to comment.