diff --git a/README.md b/README.md index 65f5fc8..84edd62 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ -# team-1 +# ChillBox -Test +> *A project by Pausetta.org, Simone Tesini, Francesco De Carlo, Leonardo Segala, Matteo Peretto* + +**ChillBox** is a web app that lets you create a shared radio station with a democratic voting system, so everyone gets to enjoy their favorite music together. +Perfect for venues like swimming pools, cafΓ©s, or even lively parties. + +--- + +## 🎡 Voting System + +Joining a ChillBox room is easy: users can either scan the QR code displayed on the host screen or use GPS to find nearby rooms. +Hosts can set a location range, ensuring only people physically present can add or vote for songs. + +--- + +## πŸ“Š Ranking Algorithm + +ChillBox uses a smart ranking algorithm to decide what plays next. The score of each song is based on: + +* Votes from users +* How recently similar songs (same genre or artist) have been played (less = better) +* A bit of randomness to keep things interesting +* A strong penalty for songs played too recently + +--- + +## πŸ‘ Hands-Off Experience + +ChillBox is designed to be almost entirely hands-free. +Once the host sets up a room and optionally connects a screen or projector +(to show the current track, QR code, etc.), ChillBox takes care of the rest. + +ChillBox comes with built-in automatic moderation to keep the music fair and on-theme. + +* Users can’t vote for the same song multiple times. +* A cooldown prevents users from spamming song requests. +* Hosts can define preferred genres and overall mood, so no one can hijack your chill beach vibes with unexpected death metal. + +That said, hosts still have access to essential controls, like pause and skip, if needed. diff --git a/SPEECH.md b/SPEECH.md new file mode 100644 index 0000000..2ab507f --- /dev/null +++ b/SPEECH.md @@ -0,0 +1,15 @@ +# speech + +## Home screen +We start here in the home page. +We can see this little radar animation, which means that the app is looking for nearby ChillBox rooms to join. +It uses GPS for this feature. + +## Join room +When we join a room, the server checks our location and checks if it's within a specified range. +That way, you must physically be in the location to actually be able to add new songs + +## Talk about the host +As you can see here (and hear) on the left, the host is already playing some music. +Now i will add a song on the client side and it will pop up in the list. + diff --git a/backend/src/app.py b/backend/src/app.py index 0fc9f9d..28c66e9 100644 --- a/backend/src/app.py +++ b/backend/src/app.py @@ -29,9 +29,9 @@ init_db(state.db) state.rooms[1000] = Room( id=1000, coord=Coordinates(46.6769043, 11.1851585), - name="Test Room", - pin=None, - tags=set(), + name="Lido Scena", + pin=1234, + tags=set(["chill", "raggaetton", "spanish", "latino", "mexican", "rock"]), range_size=150, songs={}, history=[], @@ -73,7 +73,7 @@ def on_leave(data): @app.get("/api/join") def join(): room_id = request.args.get("room") - code = request.args.get("code") + code = request.args.get("pin") if room_id is None: return error("Missing room id") @@ -81,8 +81,11 @@ def join(): if (room := state.rooms.get(int(room_id))) is None: return error("Invalid room") - if room.pin is not None and room.pin != code: - return error("Invalid code") + if room.pin is not None: + if code is None: + return error("Missing code") + if int(room.pin) != int(code): + return error("Invalid code") distance = distance_between_coords( lhs=room.coord, diff --git a/frontend/src/lib/components/RoomComponent.svelte b/frontend/src/lib/components/RoomComponent.svelte index 4e742db..0192683 100644 --- a/frontend/src/lib/components/RoomComponent.svelte +++ b/frontend/src/lib/components/RoomComponent.svelte @@ -1,19 +1,45 @@ - -
- {room.name} - {room.private ? "πŸ”’" : ""} -
-
-
-
{Math.round(room.distance)}m
-
Join
-
-
+ + {#if showPinModal} + + + {/if} + diff --git a/frontend/src/lib/components/SuggestionInput.svelte b/frontend/src/lib/components/SuggestionInput.svelte index 8cc5a9a..d19dd17 100644 --- a/frontend/src/lib/components/SuggestionInput.svelte +++ b/frontend/src/lib/components/SuggestionInput.svelte @@ -1,20 +1,39 @@
{ + errorMsg = null if (e.key == "Enter") { sendSong() } }} - {disabled} + disabled={loading} /> - {#if disabled} + {#if loading} {/if} - Add
+ +

+ {errorMsg} +

diff --git a/frontend/src/lib/components/SuggestionList.svelte b/frontend/src/lib/components/SuggestionList.svelte index 6f5d649..6c667a3 100644 --- a/frontend/src/lib/components/SuggestionList.svelte +++ b/frontend/src/lib/components/SuggestionList.svelte @@ -7,9 +7,8 @@ let picked_suggestions: string[] = $state([]) - async function vote(idx: number, amount: number, songId: string) { + async function vote(amount: number, songId: string) { if (picked_suggestions.includes(songId)) return console.log("rejecting vote") - suggestions[idx].upvote += amount await fetch(`/api/song/voting?room=${roomId}&song=${songId}&increment=${amount}`, { method: "POST" }) picked_suggestions.push(songId) console.log("accepted vote") @@ -26,7 +25,7 @@

No suggestions yet! Try to add a new one using the Add button

{/if} - {#each suggestions as sug, idx} + {#each suggestions as sug}
@@ -48,7 +47,7 @@ class={!picked_suggestions.includes(sug.uuid) ? "text-light-pine-green duration-100 hover:scale-150 dark:text-dark-pine-green" : "text-light-pine-muted dark:text-dark-pine-muted"} disabled={!!picked_suggestions.includes(sug.uuid)} onclick={async () => { - await vote(idx, 1, sug.uuid) + await vote(1, sug.uuid) }}>

{sug.upvote}

@@ -56,7 +55,7 @@ class={!picked_suggestions.includes(sug.uuid) ? "text-light-pine-red duration-100 hover:scale-150 dark:text-dark-pine-red" : "text-light-pine-muted dark:text-dark-pine-muted"} disabled={!!picked_suggestions.includes(sug.uuid)} onclick={async () => { - await vote(idx, -1, sug.uuid) + await vote(-1, sug.uuid) }}>
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 6450247..470da85 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1,8 +1,8 @@ -import { get_coords, type Coordinates } from "./gps" +import { type Coordinates } from "./gps" import { parseSong, parseSuggestion, type FetchError, type Song, type Suggestion } from "./types" -export const joinRoom = async function (roomId: string, coords: Coordinates): Promise<[FetchError | null, string]> { - let res = await fetch(`/api/join?room=${roomId}&lat=${coords.latitude}&lon=${coords.longitude}`) +export const joinRoom = async function (roomId: string, coords: Coordinates, pin: string): Promise<[FetchError | null, string]> { + let res = await fetch(`/api/join?room=${roomId}&lat=${coords.latitude}&lon=${coords.longitude}&pin=${pin}`) if (res.status != 200) { return [{ code: 400, message: "Cannot join the room" }, ""] diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index e8fb789..b0b35bf 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -23,12 +23,12 @@ ChillBox - radar + radar Scanning for rooms near you... + + {/if} diff --git a/frontend/src/routes/room/[id]/+page.svelte b/frontend/src/routes/room/[id]/+page.svelte index 76984f3..84d8d67 100644 --- a/frontend/src/routes/room/[id]/+page.svelte +++ b/frontend/src/routes/room/[id]/+page.svelte @@ -31,7 +31,7 @@ } let sugg, queue, index - ;[returnError] = await joinRoom(data.roomId, coords) + ;[returnError] = await joinRoom(data.roomId, coords, data.pin) if (returnError) { return } diff --git a/frontend/src/routes/room/[id]/+page.ts b/frontend/src/routes/room/[id]/+page.ts index 1f9f77d..f3bea98 100644 --- a/frontend/src/routes/room/[id]/+page.ts +++ b/frontend/src/routes/room/[id]/+page.ts @@ -3,5 +3,6 @@ import type { PageLoad } from "./$types" export const load: PageLoad = ({ params, url }) => { return { roomId: params.id || "", + pin: url.searchParams.get("pin") || "", } } diff --git a/frontend/src/routes/room/create/+page.svelte b/frontend/src/routes/room/create/+page.svelte index c69b19e..d0009f3 100644 --- a/frontend/src/routes/room/create/+page.svelte +++ b/frontend/src/routes/room/create/+page.svelte @@ -9,9 +9,7 @@ let name: string = $state() let range: number = $state() - - const privateStyle = "bg-red-500" - const publicStyle = "bg-green-500" + let pin: number = $state() async function createRoom() { if (creating) { @@ -22,16 +20,12 @@ return } - let pin - if (privateRoom) { - pin = Math.floor(Math.random() * 10000) - } else { - pin = "" - } - creating = true - const res = await fetch(`/api/room/new?name=${encodeURIComponent(name)}&coords=${coord.latitude},${coord.longitude}&range=${encodeURIComponent(range ?? "100")}&pin=${pin}`, { method: "POST" }) + const res = await fetch( + `/api/room/new?name=${encodeURIComponent(name)}&coords=${coord.latitude},${coord.longitude}&range=${encodeURIComponent(range ?? "100")}&pin=${encodeURIComponent(pin ?? "")}`, + { method: "POST" } + ) const json = await res.json() @@ -50,8 +44,10 @@
{coord.latitude},{coord.longitude}

-
- Public Room: - - {#if !privateRoom} -

The room is flagged as public, everyone can join

- {/if} -
+