From 99560f6eb0f4e8fc2a33dbb95c27b667d8435e4b Mon Sep 17 00:00:00 2001 From: Carlos Perez Date: Tue, 31 May 2022 13:18:01 -0300 Subject: [PATCH] First androidTV views --- android/app/build.gradle | 2 +- src/App.scss | 8 ++ src/App.tsx | 34 +++++++-- src/Components/AudioControl.scss | 10 ++- src/Components/AudioControl.tsx | 18 +++-- src/Components/NowPlaying.scss | 59 +++++++++++++++ src/Components/NowPlaying.tsx | 123 +++++++++++++++++++++++++++++++ src/Components/TVSidebar.scss | 64 ++++++++++++++++ src/Components/TVSidebar.tsx | 40 ++++++++++ src/Plugins/AndroidTV.tsx | 13 +++- 10 files changed, 353 insertions(+), 18 deletions(-) create mode 100644 src/Components/NowPlaying.scss create mode 100644 src/Components/NowPlaying.tsx create mode 100644 src/Components/TVSidebar.scss create mode 100644 src/Components/TVSidebar.tsx diff --git a/android/app/build.gradle b/android/app/build.gradle index f73236b..640f2fa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -7,7 +7,7 @@ android { applicationId "tech.logica10.soniclair" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 9 + versionCode 10 versionName "0.0.1-beta3-vlcbackend-coroutines" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { diff --git a/src/App.scss b/src/App.scss index 9c5b07f..de4e336 100644 --- a/src/App.scss +++ b/src/App.scss @@ -202,4 +202,12 @@ input[type=range]:focus { input[type=range]:focus::-webkit-slider-runnable-track { background: #737272; +} + +.container-tv{ + position:absolute; + top:0; + left: 15vw; + width:85vw; + height:100vh; } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 81b8c05..9c56db7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,6 +24,10 @@ import { Capacitor } from '@capacitor/core'; import Search from './Components/Search'; import VLC from './Plugins/VLC'; import Account from './Components/Account'; +import NowPlaying from './Components/NowPlaying'; +import AndroidTV from './Plugins/AndroidTV'; +import classNames from 'classnames'; +import TVSidebar from './Components/TVSidebar'; function App() { @@ -31,6 +35,7 @@ function App() { const [currentTrack, setCurrentTrack] = useState(CurrentTrackContextDefValue); const [playlist, setPlaylist] = useState([CurrentTrackContextDefValue]); const [menuContext, setMenuContext] = useState(MenuContextDefValue); + const [androidTv, setAndroidTv] = useState(false); const setPlaylistAndPlay = (p: IAlbumSongResponse[], track: number) => { setPlaylist(p); setCurrentTrack(p[track]); @@ -56,6 +61,10 @@ function App() { text: 'There was an error obtaining a token from spotify. Artist images may look off.', }); } + if (Capacitor.isPluginAvailable("AndroidTV")) { + setAndroidTv((await AndroidTV.get()).value); + } + const c = await VLC.getActiveAccount(); if (c.status === "ok") { @@ -68,7 +77,7 @@ function App() { if (Capacitor.getPlatform() == "android") { StatusBar.setBackgroundColor({ color: "282c34" }); } - + setTried(true); document.addEventListener("contextmenu", (event) => { event.preventDefault(); @@ -82,16 +91,20 @@ function App() { const [navbarCollapsed, setNavbarCollapsed] = useState(true); - return ( -
+ return (<> + {androidTv &&
} + +
SonicLair - - + {!androidTv && <> + + } + { context.username === "" &&
@@ -112,15 +125,22 @@ function App() { } /> } /> } /> + } /> - - + { + !androidTv && <> + + + + + } }
+ ); } diff --git a/src/Components/AudioControl.scss b/src/Components/AudioControl.scss index ba0e05b..ca6e5ff 100644 --- a/src/Components/AudioControl.scss +++ b/src/Components/AudioControl.scss @@ -1,14 +1,16 @@ -.current-track-header{ +.current-track-header { height: 10vh; overflow: hidden; margin: 0 0 0.5rem 0; + .current-track-img { max-height: 10vh; - height: auto; + height: auto; width: auto; margin: 0 0.5rem 0 0; } } -.fade-right{ + +.fade-right { // mask-image: linear-gradient(270deg, rgb(0,0,0,0) 0%,rgb(0,0,0,0) 25%,rgb(40, 44, 52) 90%,rgb(40, 44, 52) 100%); -} \ No newline at end of file +} diff --git a/src/Components/AudioControl.tsx b/src/Components/AudioControl.tsx index 23e0e1c..46870c3 100644 --- a/src/Components/AudioControl.tsx +++ b/src/Components/AudioControl.tsx @@ -6,11 +6,10 @@ import { SecondsToHHSS } from "../Helpers"; import "./AudioControl.scss"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import _, { } from 'lodash'; -import { useNavigate } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import classnames from "classnames"; import VLC from "../Plugins/VLC"; import { Backend } from "../Plugins/Audio"; -// import AndroidTV from "../Plugins/AndroidTV"; import { Capacitor } from "@capacitor/core"; import AndroidTV from "../Plugins/AndroidTV"; @@ -19,7 +18,6 @@ interface IListener { func: (ev: any) => void; } - export default function AudioControl({ }) { const { currentTrack, setCurrentTrack } = useContext(CurrentTrackContext); const [isPlaying, setIsPlaying] = useState(false); @@ -27,7 +25,7 @@ export default function AudioControl({ }) { const [coverArt, setCoverArt] = useState(""); const audioInstance = useRef(new Backend()); const [androidTV, setAndroidTV] = useState(false); - + const location = useLocation(); const navigate = useNavigate(); const [volume, setVolume] = useState(1); @@ -112,8 +110,15 @@ export default function AudioControl({ }) { navigate(`/album`, { state: { id: currentTrack.parent } }); }, [currentTrack]); + const hide = useCallback(() => { + if(currentTrack.id === "" || location.pathname.match(/playing/)){ + return "d-none" + } + return "d-flex"; + },[currentTrack, location.pathname]); + return ( -
+
{/*
*/}
@@ -126,6 +131,9 @@ export default function AudioControl({ }) { {/*
*/}
+ diff --git a/src/Components/NowPlaying.scss b/src/Components/NowPlaying.scss new file mode 100644 index 0000000..570086b --- /dev/null +++ b/src/Components/NowPlaying.scss @@ -0,0 +1,59 @@ +.current-track-img-tv { + max-height: 40vh; + height: auto; + width: auto; + margin: 0 0.5rem 0 0; + border-radius: 5%; + box-shadow: + 0 0 10px 5px #fff, + 0 0 15px 10px #0ff; + animation: pulse 2s infinite; +} + +.current-track-header { + overflow: hidden; + margin: 0 0 0.5rem 0; + + +} + +.fade-right { + // mask-image: linear-gradient(270deg, rgb(0,0,0,0) 0%,rgb(0,0,0,0) 25%,rgb(40, 44, 52) 90%,rgb(40, 44, 52) 100%); +} + +.shadow-down { + width: 100vw; + height: 1px; + position: absolute; + left: 0; + top: calc(100vh - 1px); + box-shadow: + 0 0 15px 10px #fff, + 0 0 20px 15px #0ff, +} + +@keyframes pulse { + 0% { + box-shadow: + 0 0 15px 10px #fff, + 0 0 20px 15px #0ff, + } + + 25% { + box-shadow: + 0 0 15px 10px #0ff, + 0 0 20px 15px #7df, + } + + 50% { + box-shadow: + 0 0 15px 10px #7df, + 0 0 20px 15px #478499, + } + + 75% { + box-shadow: + 0 0 15px 10px #478499, + 0 0 20px 15px #fff, + } +} \ No newline at end of file diff --git a/src/Components/NowPlaying.tsx b/src/Components/NowPlaying.tsx new file mode 100644 index 0000000..936c87e --- /dev/null +++ b/src/Components/NowPlaying.tsx @@ -0,0 +1,123 @@ +import { faForwardStep, faPause, faPlay, faRotateLeft, faRotateRight } from "@fortawesome/free-solid-svg-icons"; +import { ChangeEvent, useCallback, useContext, useEffect, useState } from "react"; +import { CurrentTrackContext } from "../AudioContext" +import { SecondsToHHSS } from "../Helpers"; +import "./NowPlaying.scss"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import _, { } from 'lodash'; +import { useNavigate } from "react-router-dom"; +import VLC from "../Plugins/VLC"; +// import AndroidTV from "../Plugins/AndroidTV"; + +export default function NowPlaying({ }) { + const { currentTrack, setCurrentTrack } = useContext(CurrentTrackContext); + const [isPlaying, setIsPlaying] = useState(false); + const [playTime, setPlayTime] = useState(0); + const [coverArt, setCoverArt] = useState(""); + const [androidTV, setAndroidTV] = useState(false); + + + const changePlayTime = useCallback((e: ChangeEvent): void => { + const time = parseFloat(e.target.value); + VLC.seek({ time: time }); + }, [currentTrack]); + + useEffect(() => { + if (currentTrack.id === "") { + return; + } + const fetch = async () => { + setCoverArt((await VLC.getAlbumArt({ id: currentTrack.coverArt })).value!); + } + fetch(); + }, [currentTrack, coverArt]); + + const playNext = useCallback(() => { + VLC.next(); + }, []); + + const playPrev = useCallback(() => { + VLC.prev(); + }, []); + + const togglePlaying = () => { + if (isPlaying) { + VLC.pause(); + } + else { + VLC.play(); + } + }; + + useEffect(() => { + (VLC as any).removeAllListeners(); + // I'm sorry typescript gods. + + (VLC as any).addListener('play', (info: any) => { + setIsPlaying(true); + }); + (VLC as any).addListener('paused', (info: any) => { + setIsPlaying(false); + }); + (VLC as any).addListener('stopped', (info: any) => { + setIsPlaying(false); + }); + (VLC as any).addListener('progress', (info: any) => { + setPlayTime(info.time); + }); + (VLC as any).addListener('currentTrack', (info: any) => { + setCurrentTrack(info.currentTrack); + }); + + return () => { + //setCurrentTrack(CurrentTrackContextDefValue); + + } + }, [currentTrack, isPlaying, setIsPlaying, setCurrentTrack]); + + return ( +
+
+ +
+
+ {currentTrack.title} + {currentTrack.album} - by {currentTrack.artist} +
+
+
+
+ + + + + +
+
+ {SecondsToHHSS((playTime ?? 0) * (currentTrack?.duration ?? 0))} + {SecondsToHHSS(currentTrack.duration)} +
+
+ changePlayTime(e)}> +
+
+ ) +} \ No newline at end of file diff --git a/src/Components/TVSidebar.scss b/src/Components/TVSidebar.scss new file mode 100644 index 0000000..d0dd6b6 --- /dev/null +++ b/src/Components/TVSidebar.scss @@ -0,0 +1,64 @@ +@import "../Styles/colors.scss"; + +.sidebar-tv { + width: 15vw; + height: 100vh; + position: absolute; + left: 0; + top: 0; + + transition: ease 0.3s; + margin: 0 0 0 0; + + .sidebar-item { + width: 15vw; + height: 4rem; + background-color: $items-color; + margin-bottom: 0.5rem; + transition: ease 0.3s; + background: linear-gradient(to right, rgba(0, 0, 0, 0),rgba(40, 44, 52, 1)); + &:hover { + background-color: $items-color-hover; + } + + .sidebar-item.focused{ + background-color: $items-color-hover; + } + + svg { + font-size: 2.5rem; + } + .item-text{ + margin-left: 10px; + font-size: large; + } + } + + + .sidebar-item-borderless { + width: 4rem; + height: 4rem; + border-radius: 10%; + margin-bottom: 0.5rem; + transition: ease 0.3s; + + &:hover { + background-color: $items-color-hover; + } + + svg { + font-size: 2.5rem; + } + } +} + +.modal-cover{ + position:fixed; + left:0; + top:0; + background-color: $item-background-translucid; + width:100vw; + height:100vh; + z-index: 2000; + transition: ease 0.1s; +} \ No newline at end of file diff --git a/src/Components/TVSidebar.tsx b/src/Components/TVSidebar.tsx new file mode 100644 index 0000000..0b5df25 --- /dev/null +++ b/src/Components/TVSidebar.tsx @@ -0,0 +1,40 @@ +import { faHouseChimney, faMagnifyingGlass, faUserAlt } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import "./TVSidebar.scss"; +import { useNavigate } from "react-router-dom"; +import { faPlayCircle } from "@fortawesome/free-regular-svg-icons"; + + +export default function TVSidebar() { + const navigate = useNavigate(); + + const nav = (path: string) => { + navigate(path); + }; + + return ( +
+
nav("/home")} + className="sidebar-item d-flex align-items-center justify-content-start text-white p-3 mt-3"> + + Home +
+
nav("/search")} + className="sidebar-item d-flex align-items-center justify-content-start text-white p-3"> + + Search +
+
nav("/account")} + className="sidebar-item last-item d-flex align-items-center justify-content-start text-white p-3"> + + Account +
+
+
nav("/playing")} + className="sidebar-item last-item d-flex align-items-center justify-content-start text-white p-3 mb-3"> + + Playing +
+
+ ) +} diff --git a/src/Plugins/AndroidTV.tsx b/src/Plugins/AndroidTV.tsx index cb47b4e..88b2408 100644 --- a/src/Plugins/AndroidTV.tsx +++ b/src/Plugins/AndroidTV.tsx @@ -8,6 +8,17 @@ export interface IBackendPlugin { get(): Promise; } -const AndroidTV = registerPlugin('AndroidTV'); +class AndroidTVPlugin implements IBackendPlugin { + constructor() { + + } + get(): Promise { + return Promise.resolve({ value: true }); + } +} + +const AndroidTV = registerPlugin('AndroidTV', { + web: () => new AndroidTVPlugin(), +}); export default AndroidTV; \ No newline at end of file