Skip to content

Commit

Permalink
UX improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Adams authored and Sam Adams committed Jul 15, 2024
1 parent b42244b commit 4bf50cb
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 45 deletions.
63 changes: 56 additions & 7 deletions src/practaid/auth.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@

(def client-id "5c6be04474984ab2a2a492967aa87002")

(def scope (str/join " "
[;; Control playback of a Spotify track.
;; This scope is currently available to the Web Playback SDK.
;; The user must have a Spotify Premium account.
"streaming"
;; Read access to a user’s recently played tracks.
;; Also for getting currently-playing
"user-read-recently-played"
;; Read your currently playing content and Spotify Connect devices information.
"user-read-playback-state"
;; Control playback on your Spotify clients and Spotify Connect devices.
"user-modify-playback-state"
;; Determine whether the user has Spotify premium
"user-read-private"
;; Including this prevents a seemingly benign console error, but
;; we don't actually need it
#_"user-read-email"]))



(defn random-bytes [n]
(-> js/crypto
(.getRandomValues (js/Uint8Array. n))))
Expand Down Expand Up @@ -38,7 +58,7 @@
url (str "https://accounts.spotify.com/authorize?"
(js/URLSearchParams. (clj->js {"response_type" "code"
"client_id" client-id
"scope" "streaming user-read-recently-played user-read-private user-read-email user-read-playback-state user-modify-playback-state user-read-currently-playing app-remote-control"
"scope" scope
"redirect_uri" (str location-origin "/callback")
"state" nonce
"code_challenge_method" "S256"
Expand Down Expand Up @@ -97,16 +117,45 @@
.getTime
(+ (* 1000 expires-in-seconds))))))

(rf/reg-event-fx
::fetch-profile
[(rf/inject-cofx ::location-origin)
inject-store
check-db-spec-interceptor]
(fn [{:keys [db store location-origin]} _]
(let [code (-> db :practaid.routes/state :current-route :query-params :code)
code-verifier (-> store :code-verifier)]
{:http-xhrio {:method :get
:uri "https://api.spotify.com/v1/me"
:headers {"Authorization" (str "Bearer " (:access-token store))
"Content-Type" "application/json"}
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::handle-fetched-profile]
:on-failure [:practaid.common/http-request-failure]}})))


(rf/reg-event-fx
::confirm-complete-auth-flow
[(rf/inject-cofx :store)
check-db-spec-interceptor]
(fn [{:keys [db store]} [_ {:keys [access_token expires_in refresh_token token_type scope]}]]
{:db (assoc db :is-authorized true)
:fx [[:store (assoc store :access-token access_token
:expires-at (expires-at (js/Date.) expires_in))]
[:dispatch [:practaid.looper/initialize-looper-page]]
[:dispatch [:practaid.routes/navigate :routes/home]]]}))
(fn [{:keys [db store]} [_ {:as response :keys [access_token expires_in refresh_token token_type scope]}]]
(let [has-premium :maybe]
{:db (assoc db :is-authorized true
:has-premium has-premium)
:fx [[:store (assoc store :access-token access_token
:expires-at (expires-at (js/Date.) expires_in))]
[:dispatch [::fetch-profile]]
[:dispatch [:practaid.looper/initialize-looper-page]]
[:dispatch [:practaid.routes/navigate :routes/home]]]})))

(rf/reg-event-fx
::handle-fetched-profile
[(rf/inject-cofx :store)
check-db-spec-interceptor]
(fn [{:keys [db store]} [_ {:as response :keys [product]}]]
{:db (assoc db :has-premium (str/includes? product "premium"))}))



(rf/reg-event-fx
::logout
Expand Down
5 changes: 5 additions & 0 deletions src/practaid/subs.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
(fn [db]
(get-in db [::player/state :device-id])))

(rf/reg-sub
::has-premium
(fn [db]
(get-in db [:has-premium])))

(rf/reg-sub
::is-taking-over-playback
(fn [db]
Expand Down
81 changes: 44 additions & 37 deletions src/practaid/views/home_page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[spade.core :refer [defclass]]
[re-frame.core :as rf]))

(def spotify-green "#1DB954")
(def pleasant-blue "#0C87E5FF")

(defclass home-page-style []
{}
Expand All @@ -13,18 +13,19 @@
;:flex-direction "column"}]
[:.button-container {:display "flex"
:justify-content "center"
:align-items "center"}]
[:.spotify {:color spotify-green
:font-weight "bold"}]
[:.login-button {:margin "auto"
:text-align "center"
:background-color spotify-green
:color "white"
:font-size "20px"
:cursor "pointer"
:border "none"
:padding "10px 15px"
:border-radius "10px"}]
:align-items "center"
:padding-bottom "10px"}]
[:img.spotify-logo {:width "100px"
:vertical-align "middle"}]
[:.login-button {:margin "auto"
:text-align "center"
:background-color pleasant-blue
:color "white"
:font-size "20px"
:cursor "pointer"
:border "none"
:padding "10px 15px"
:border-radius "10px"}]
[:.beta {:text-align "center"}]
[:.beta-note {:color "red"}]
[:.subsection {:border "1px solid black"
Expand All @@ -45,7 +46,8 @@
[:span {:style {:margin-right l-pad
:transform "rotate(0deg)"
:display "inline-block"}} boomerang]
children
(for [child children]
^{:key child} [:span child])
[:span {:style {:margin-left r-pad
:transform "rotate(180deg)"
:display "inline-block"}} boomerang]]))
Expand All @@ -65,33 +67,38 @@
[page-wrapper
[:div.home-page {:class (home-page-style)}
[:div.explanation
[:p "Welcome to PractAid, an open source music practice tool integrated with "
[:a.spotify {:href "https://www.spotify.com/" :target "_blank"} "Spotify"]
"."]
[:p [boomeranged "PractAid lets you loop segments of Spotify songs."]]
[:p [robotted [:span "Check out the source code " [:a {:href "https://github.com/samharad/practaid" :target "_blank"} "here"] "."]]]
[:p "PractAid is a music practice tool."]
[:p [boomeranged
[:span
"It lets you loop segments of "
[:a {:href (str "https://spotify.com")
:title "Spotify"}
[:img.spotify-logo
{:src "/image/Spotify_Logo_RGB_Green.png"}]]
" songs."]]]
[:p "Happy practicing!"]]
[:div.button-container
[:button.login-button {:type "button"
:on-click handle-login-click}
"Log In to Spotify"]]
[:div.beta
[:p
[:b [:span.beta-note "Note:"] " this app is in "
[:a {:href "https://developer.spotify.com/documentation/web-api/guides/development-extended-quota-modes/"} "development mode"]
" and only accessible to allow-listed users."]]
[:p "To request access, send an email including your Spotify username/email to "
[:a {:href "mailto: support@practaid.com?subject=Request for Access to PractAid&body=My Spotify username is {PUT YOUR USERNAME HERE}"}
"support@practaid.com"]
"."]
[:div.subsection
[:div
[:p "PractAid requires permission from your "
[:img.spotify-logo
{:src "/image/Spotify_Logo_RGB_Green.png"}]
" account."]]
[:div.button-container
[:button.login-button {:type "button"
:on-click handle-login-click}
"Authenticate with Spotify"]]]
[:div.subsection
[:div
[:p.header "Privacy Policy"]
[:p "You'll need to log in with Spotify to use this tool."]
[:p "We don't read, collect, store, or use any of your data."]
[:p "(We don't even have a backend or database -- your browser talks directly to Spotify, and that's it.)"]]]
[:p
"You'll need to log in with "
[:a {:href (str "https://spotify.com")
:title "Spotify"}
[:img.spotify-logo
{:src "/image/Spotify_Logo_RGB_Green.png"}]]
" to use this tool."]
[:p "We don't read, collect, store, or use any of your data."]]]
[:div.subsection
[:p.header "Why?"]
[:p "I created this for myself to solve my own pain point."]
[:p "I often play guitar along with a Spotify song; sometimes I want to loop a section to practice it repeatedly."]
[:p "I'm releasing it in case someone else finds it useful. (It's hosted for free on GitHub Pages, so why not?)"]]]]])
[:p "I created this for myself as an aid to guitar practice."]]]]])
11 changes: 10 additions & 1 deletion src/practaid/views/looper_page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@

(defn looper-page []
(let [device-id @(rf/subscribe [:practaid.subs/device-id])
has-premium @(rf/subscribe [:practaid.subs/has-premium])
is-taking-over-playback @(rf/subscribe [:practaid.subs/is-taking-over-playback])
playback-md @(rf/subscribe [:practaid.subs/playback-metadata])
track @(rf/subscribe [:practaid.subs/track])
Expand All @@ -189,10 +190,15 @@
[page-wrapper
[:div {:class (looper-page-style)}
[:div.connection-status
(and (not has-premium)
[:h3 "Sorry, a premium subscription is required for using this tool."])
;; TODO make me dumber
(and (not device-id) [:h3.connecting "Connecting"])
(and (not device-id)
has-premium
[:h3.connecting "Connecting"])
(and device-id
(not is-playback-ours)
has-premium
[:div
[:h3.connected "Connected"]
[:div.button-wrapper
Expand All @@ -206,13 +212,16 @@
(rf/dispatch [:practaid.player/takeover-playback]))}
"Take over playback" (and is-taking-over-playback [loading-ellipses])]]])]
(and playback-md
has-premium
[playback-metadata playback-md])
(and track
has-premium
[playback-controls {:disabled (not is-playback-ours)
:on-toggle-play #(rf/dispatch [:practaid.looper/toggle-play])
:on-next #(rf/dispatch [:practaid.looper/next-track])
:on-prev #(rf/dispatch [:practaid.looper/prev-track])}])
(and loudnesses
has-premium
[:div.field
[slider {:min 0
:max track-duration-ms
Expand Down

0 comments on commit 4bf50cb

Please # to comment.