├── android ├── app │ ├── .gitignore │ ├── releasekey.jks │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── xml │ │ │ │ ├── automotive_app_desc.xml │ │ │ │ ├── config.xml │ │ │ │ └── file_paths.xml │ │ │ ├── drawable │ │ │ │ ├── splash.png │ │ │ │ ├── ic_album_art_placeholder.png │ │ │ │ ├── round_outline.xml │ │ │ │ ├── round_outline_selected.xml │ │ │ │ ├── ic_pause_icon.xml │ │ │ │ ├── round_outline_selector.xml │ │ │ │ ├── ic_play.xml │ │ │ │ ├── ic_skip_back.xml │ │ │ │ ├── ic_skip_forward.xml │ │ │ │ ├── ic_smartphone_line.xml │ │ │ │ ├── ic_user.xml │ │ │ │ ├── ic_disc.xml │ │ │ │ ├── ic_play_list.xml │ │ │ │ ├── ic_speed.xml │ │ │ │ ├── ic_rewind.xml │ │ │ │ ├── ic_home.xml │ │ │ │ ├── ic_play_circle.xml │ │ │ │ ├── ic_shuffle_fill.xml │ │ │ │ ├── ic_shuffle_fill_primary.xml │ │ │ │ ├── ic_search.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── ic_logo.xml │ │ │ ├── font │ │ │ │ └── montserrat.ttf │ │ │ ├── drawable-land │ │ │ │ └── splash.png │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_pause.png │ │ │ │ ├── ic_play_arrow.png │ │ │ │ ├── ic_skip_next.png │ │ │ │ ├── ic_action_cancel.png │ │ │ │ ├── ic_skip_previous.png │ │ │ │ └── ic_stat_soniclair.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_pause.png │ │ │ │ ├── ic_play_arrow.png │ │ │ │ ├── ic_skip_next.png │ │ │ │ ├── ic_action_cancel.png │ │ │ │ ├── ic_skip_previous.png │ │ │ │ └── ic_stat_soniclair.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_pause.png │ │ │ │ ├── ic_app_banner.png │ │ │ │ ├── ic_play_arrow.png │ │ │ │ ├── ic_skip_next.png │ │ │ │ ├── ic_action_cancel.png │ │ │ │ ├── ic_skip_previous.png │ │ │ │ └── ic_stat_soniclair.png │ │ │ ├── drawable-land-hdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-land-ldpi │ │ │ │ └── splash.png │ │ │ ├── drawable-land-mdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-hdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-ldpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-mdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_pause.png │ │ │ │ ├── ic_skip_next.png │ │ │ │ ├── ic_play_arrow.png │ │ │ │ ├── ic_action_cancel.png │ │ │ │ ├── ic_skip_previous.png │ │ │ │ └── ic_stat_soniclair.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_pause.png │ │ │ │ ├── ic_play_arrow.png │ │ │ │ ├── ic_skip_next.png │ │ │ │ ├── ic_action_cancel.png │ │ │ │ ├── ic_skip_previous.png │ │ │ │ └── ic_stat_soniclair.png │ │ │ ├── drawable-land-xhdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-land-xxhdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-xhdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-xxhdpi │ │ │ │ └── splash.png │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ └── splash.png │ │ │ ├── mipmap-hdpi-v26 │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ ├── ic_launcher_adaptive_back.png │ │ │ │ └── ic_launcher_adaptive_fore.png │ │ │ ├── mipmap-mdpi-v26 │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ ├── ic_launcher_adaptive_back.png │ │ │ │ └── ic_launcher_adaptive_fore.png │ │ │ ├── mipmap-xhdpi-v26 │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ ├── ic_launcher_adaptive_back.png │ │ │ │ └── ic_launcher_adaptive_fore.png │ │ │ ├── mipmap-xxhdpi-v26 │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ ├── ic_launcher_adaptive_back.png │ │ │ │ └── ic_launcher_adaptive_fore.png │ │ │ ├── mipmap-xxxhdpi-v26 │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ ├── ic_launcher_adaptive_back.png │ │ │ │ └── ic_launcher_adaptive_fore.png │ │ │ ├── values │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ └── ic_launcher.xml │ │ │ ├── color │ │ │ │ └── background_color_selector.xml │ │ │ └── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── fragment_playlists.xml │ │ │ │ ├── fragment_jukebox.xml │ │ │ │ ├── album_card.xml │ │ │ │ ├── fragment_account.xml │ │ │ │ ├── playlist_item.xml │ │ │ │ ├── fragment_home.xml │ │ │ │ └── fragment_search.xml │ │ │ ├── kotlin │ │ │ └── tech │ │ │ │ └── logica10 │ │ │ │ └── soniclair │ │ │ │ ├── models │ │ │ │ ├── AlbumList2.kt │ │ │ │ ├── RandomSongs.kt │ │ │ │ ├── SimilarSongs.kt │ │ │ │ ├── JukeboxLogin.kt │ │ │ │ ├── Settings.kt │ │ │ │ ├── SongResponse.kt │ │ │ │ ├── AlbumsResponse.kt │ │ │ │ ├── SearchType.kt │ │ │ │ ├── ArtistInfoResponse.kt │ │ │ │ ├── ArtistSubsonicResponse.kt │ │ │ │ ├── ArtistIndex.kt │ │ │ │ ├── RandomSongsResponse.kt │ │ │ │ ├── SearchResponse.kt │ │ │ │ ├── SimilarSongsResponse.kt │ │ │ │ ├── WebSocketCommand.kt │ │ │ │ ├── SubsonicError.kt │ │ │ │ ├── ArtistsSubsonicResponse.kt │ │ │ │ ├── InnerArtistsSubsonicResponse.kt │ │ │ │ ├── ParameterException.kt │ │ │ │ ├── BackendResponse.kt │ │ │ │ ├── CardViewModel.kt │ │ │ │ ├── SetPlaylistAndPlayRequest.kt │ │ │ │ ├── WebSocketMessage.kt │ │ │ │ ├── Context.kt │ │ │ │ ├── ArtistListItem.kt │ │ │ │ ├── ArtistInfo.kt │ │ │ │ ├── SearchResult.kt │ │ │ │ ├── SubsonicResponse.kt │ │ │ │ ├── Account.kt │ │ │ │ ├── Artist.kt │ │ │ │ ├── AlbumWithSongs.kt │ │ │ │ ├── BasicParams.kt │ │ │ │ ├── Song.kt │ │ │ │ ├── Album.kt │ │ │ │ └── AlbumResponse.kt │ │ │ │ ├── WebSocketNotification.kt │ │ │ │ ├── CurrentState.kt │ │ │ │ ├── extensions │ │ │ │ └── Extensions.kt │ │ │ │ ├── NotificationBroadcastReceiver.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── room │ │ │ │ ├── daos │ │ │ │ │ ├── ArtistDao.kt │ │ │ │ │ ├── AlbumDao.kt │ │ │ │ │ └── SongDao.kt │ │ │ │ └── database │ │ │ │ │ └── SoniclairDatabase.kt │ │ │ │ ├── Helpers.kt │ │ │ │ ├── fragments │ │ │ │ ├── JukeboxFragment.kt │ │ │ │ └── PlaylistsFragment.kt │ │ │ │ ├── AndroidTVPlugin.kt │ │ │ │ ├── adapters │ │ │ │ └── SoniclairCardAdapter.kt │ │ │ │ └── App.kt │ │ │ ├── assets │ │ │ ├── capacitor.config.json │ │ │ └── capacitor.plugins.json │ │ │ └── java │ │ │ └── tech │ │ │ └── logica10 │ │ │ └── soniclair │ │ │ ├── IBroadcastObserver.java │ │ │ ├── AccountFragment.kt │ │ │ └── Globals.java │ ├── capacitor.build.gradle │ └── proguard-rules.pro ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .idea │ ├── codeStyles │ │ └── codeStyleConfig.xml │ ├── compiler.xml │ ├── inspectionProfiles │ │ └── Project_Default.xml │ └── jarRepositories.xml ├── settings.gradle ├── variables.gradle ├── build.gradle ├── capacitor.settings.gradle ├── gradle.properties ├── .gitignore └── gradlew.bat ├── .prettierrc ├── src-tauri ├── build.rs ├── icons │ ├── icon.ico │ ├── icon.png │ ├── 128x128.png │ ├── 32x32.png │ ├── icon.icns │ ├── StoreLogo.png │ ├── 128x128@2x.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ └── Square89x89Logo.png ├── .gitignore ├── src │ └── main.rs ├── Cargo.toml └── tauri.conf.json ├── src ├── react-app-env.d.ts ├── reactvirtualgrid.d.ts ├── Components │ ├── SongItem.scss │ ├── Playlist.scss │ ├── Navbar.scss │ ├── Loading.tsx │ ├── CardContextMenu.scss │ ├── Album.scss │ ├── PlaylistEntry.scss │ ├── Account.scss │ ├── DraggableList.scss │ ├── CardContextMenu.tsx │ ├── AudioControl.scss │ ├── Artists.scss │ ├── Loading.scss │ ├── AccountItem.scss │ ├── Artist.scss │ ├── ArtistCard.scss │ ├── AlbumCard.scss │ ├── Sidebar.scss │ ├── TVJukebox.tsx │ ├── TVTopBar.tsx │ ├── NowPlaying.scss │ ├── PlaylistItem.tsx │ ├── AccountItem.tsx │ ├── RandomSongCard.tsx │ ├── PlaylistItemCard.tsx │ ├── TVSidebar.scss │ ├── TVPlaylists.tsx │ ├── TVSidebar.tsx │ └── Navbar.tsx ├── Models │ ├── API │ │ ├── Responses │ │ │ ├── IArtist.tsx │ │ │ ├── Index.tsx │ │ │ ├── SubsonicResponse.tsx │ │ │ ├── IAlbumsResponse.tsx │ │ │ ├── IAlbumInfoResponse.tsx │ │ │ ├── IArtistsResponse.tsx │ │ │ ├── IPlaylistsResponse.tsx │ │ │ ├── ISpotifyResponse.tsx │ │ │ ├── IArtistInfoResponse.tsx │ │ │ └── IArtistResponse.tsx │ │ └── Requests │ │ │ └── BasicParams.tsx │ ├── IGridProps.ts │ └── AppContext.tsx ├── setupTests.ts ├── App.test.tsx ├── Styles │ └── colors.scss ├── index.css ├── reportWebVitals.ts ├── Hooks │ ├── useWindowSize.ts │ └── useAutoFill.ts ├── AudioContext.tsx ├── Api │ └── GetSpotifyToken.ts ├── Plugins │ └── AndroidTV.tsx ├── index.tsx ├── AppContext.tsx └── Helpers.ts ├── public ├── robots.txt ├── manifest.json └── index.html ├── assets ├── tvScreenshot1.png ├── tvScreenshot2.png ├── tvScreenshot3.png ├── tvScreenshot4.png ├── phoneScreenshot1.jpg ├── phoneScreenshot2.jpg ├── phoneScreenshot3.jpg └── phoneScreenshot4.jpg ├── cypress └── videos │ └── spec.cy.ts.mp4 ├── changelog.md ├── capacitor.config.ts ├── .gitignore ├── tsconfig.json ├── LICENSE ├── SonicLairAndroid.gocd.yaml ├── storelogo.svg ├── .github └── workflows │ └── android-build.yaml ├── package.json ├── ghlogo.svg └── logo.svg /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reactvirtualgrid.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-responsive-virtual-grid'; -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /assets/tvScreenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/tvScreenshot1.png -------------------------------------------------------------------------------- /assets/tvScreenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/tvScreenshot2.png -------------------------------------------------------------------------------- /assets/tvScreenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/tvScreenshot3.png -------------------------------------------------------------------------------- /assets/tvScreenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/tvScreenshot4.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /android/app/releasekey.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/releasekey.jks -------------------------------------------------------------------------------- /assets/phoneScreenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/phoneScreenshot1.jpg -------------------------------------------------------------------------------- /assets/phoneScreenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/phoneScreenshot2.jpg -------------------------------------------------------------------------------- /assets/phoneScreenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/phoneScreenshot3.jpg -------------------------------------------------------------------------------- /assets/phoneScreenshot4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/assets/phoneScreenshot4.jpg -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /android/app/src/main/res/xml/automotive_app_desc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /cypress/videos/spec.cy.ts.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/cypress/videos/spec.cy.ts.mp4 -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | WixTools 5 | -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/font/montserrat.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/font/montserrat.ttf -------------------------------------------------------------------------------- /src/Components/SongItem.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .highlight { 4 | background-color: $items-color-highlight !important; 5 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land/splash.png -------------------------------------------------------------------------------- /src/Components/Playlist.scss: -------------------------------------------------------------------------------- 1 | .playlist-container{ 2 | height: 100%; 3 | overflow: hidden; 4 | align-items: start; 5 | position: relative; 6 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/AlbumList2.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class AlbumList2(val album: List) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/RandomSongs.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class RandomSongs(val song: List) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SimilarSongs.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SimilarSongs(val song: List) -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_pause.png -------------------------------------------------------------------------------- /src/Components/Navbar.scss: -------------------------------------------------------------------------------- 1 | .sonic-navbar{ 2 | height:calc(3rem + 10px); 3 | margin-top: 0.5rem; 4 | svg { 5 | font-size: 2.5rem; 6 | } 7 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-ldpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-ldpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-ldpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-ldpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_pause.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_skip_next.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_skip_next.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_app_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_app_banner.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_skip_next.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_skip_next.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi-v26/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi-v26/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-mdpi-v26/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/JukeboxLogin.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class JukeboxLogin(val account: Account, val ip: String) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Settings.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class Settings(val cacheSize: Int, val transcoding: String) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SongResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SongResponse(val song: Song) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_action_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_action_cancel.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_skip_previous.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_action_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_action_cancel.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_skip_previous.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_play_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_play_arrow.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_skip_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_skip_next.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_stat_soniclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-hdpi/ic_stat_soniclair.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_stat_soniclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-mdpi/ic_stat_soniclair.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_action_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_action_cancel.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_skip_previous.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_stat_soniclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xhdpi/ic_stat_soniclair.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_skip_previous.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_stat_soniclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_stat_soniclair.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_action_cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_action_cancel.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_skip_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_skip_previous.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_album_art_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable/ic_album_art_placeholder.png -------------------------------------------------------------------------------- /src/Components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import "./Loading.scss"; 2 | export default function Loading() { 3 | return
4 | } -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "tech.logica10.soniclair", 3 | "appName": "SonicLair", 4 | "webDir": "build", 5 | "bundledWebRuntime": false 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_stat_soniclair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_stat_soniclair.png -------------------------------------------------------------------------------- /src/Models/API/Responses/IArtist.tsx: -------------------------------------------------------------------------------- 1 | 2 | export interface IArtist { 3 | id: string; 4 | name: string; 5 | albumCount: number; 6 | artistImageUrl: string; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/WebSocketNotification.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | class WebSocketNotification(val action: String, val value: String?) 4 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/AlbumsResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class AlbumsResponse(val albumList2: AlbumList2) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SearchType.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | enum class SearchType { 4 | ARTIST, 5 | ALBUM, 6 | SONG 7 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistInfoResponse(val artistInfo2: ArtistInfo) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistSubsonicResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistSubsonicResponse(val artist: Artist) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-hdpi-v26/ic_launcher_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-mdpi-v26/ic_launcher_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xhdpi-v26/ic_launcher_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxhdpi-v26/ic_launcher_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/java/tech/logica10/soniclair/IBroadcastObserver.java: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair; 2 | 3 | public interface IBroadcastObserver { 4 | void update(String action, String value); 5 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistIndex.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistIndex(val name: String, val length: Int, val artist: List) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/RandomSongsResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class RandomSongsResponse(val randomSongs: RandomSongs) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_adaptive_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_adaptive_back.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_adaptive_fore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thelinkin3000/SonicLair/HEAD/android/app/src/main/res/mipmap-xxxhdpi-v26/ic_launcher_adaptive_fore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SearchResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SearchResponse( 4 | val searchResult3: SearchResult 5 | ) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SimilarSongsResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SimilarSongsResponse(val similarSongs2: SimilarSongs) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/WebSocketCommand.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class WebSocketCommand( 4 | val command: String, 5 | val data: String 6 | ) -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SubsonicError.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SubsonicError { 4 | val message: String = "" 5 | val code: Int = 0 6 | 7 | } -------------------------------------------------------------------------------- /src/Models/API/Responses/Index.tsx: -------------------------------------------------------------------------------- 1 | import { IArtist } from "./IArtist"; 2 | 3 | export interface IArtistIndex { 4 | name: string; 5 | artist: IArtist[]; 6 | length: number; 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistsSubsonicResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistsSubsonicResponse(val artists: InnerArtistsSubsonicResponse) : SubsonicResponse() -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/InnerArtistsSubsonicResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class InnerArtistsSubsonicResponse(val ignoredArticles: String, val index: List) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ParameterException.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ParameterException(val parameter: String): Exception("The $parameter parameter cannot be empty") -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # New Release! 2 | 3 | ## Bugfixes 4 | 5 | Fixed a bug that crashed the app if it had no connection to the server 6 | Fixed a bug that displayed unnecesary messages while reconnecting to the server via websockets 7 | -------------------------------------------------------------------------------- /src/Components/CardContextMenu.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .context-menu { 4 | position: absolute; 5 | padding: 10px; 6 | background-color: $color-background-darker; 7 | border-radius: 10px; 8 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/BackendResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class BackendResponse(val value: T) { 4 | val status: String = "" 5 | val error: String = "" 6 | } -------------------------------------------------------------------------------- /android/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40dp 4 | 15sp 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/CardViewModel.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | interface ICardViewModel { 4 | fun firstLine(): String 5 | fun secondLine(): String 6 | var image: String 7 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SetPlaylistAndPlayRequest.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class SetPlaylistAndPlayRequest(val playlist: Playlist, val track: Int, val seek: Float, val playing: Boolean) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/WebSocketMessage.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class WebSocketMessage( 4 | val data: String, 5 | val type: String, 6 | val status: String, 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/round_outline.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/round_outline_selected.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Context.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class Context( 4 | val accounts: List = emptyList(), 5 | val activeAccount: Account = Account(null, "", "", "", false), 6 | ) -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistListItem.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistListItem( 4 | val id: String, 5 | val name: String, 6 | val albumCount: Int, 7 | val artistImageUrl: String 8 | ) -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/Components/Album.scss: -------------------------------------------------------------------------------- 1 | .album-header{ 2 | position:relative; 3 | max-height: 20vh; 4 | .album-img { 5 | margin: 0 0.5rem 0 0; 6 | } 7 | div{ 8 | span{ 9 | overflow: hidden; 10 | 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Models/IGridProps.ts: -------------------------------------------------------------------------------- 1 | export default interface IGridProps { 2 | columnCount: number; 3 | columnWidth: number; 4 | height: number; 5 | rowCount: number; 6 | rowHeight: number; 7 | width: number; 8 | className: string; 9 | 10 | } -------------------------------------------------------------------------------- /src/Models/API/Responses/SubsonicResponse.tsx: -------------------------------------------------------------------------------- 1 | export interface ISubsonicResponse{ 2 | serverVersion:string; 3 | status:string; 4 | type:string; 5 | version:string; 6 | error?:{ 7 | message:string, 8 | code: number, 9 | }; 10 | } -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/ArtistInfo.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class ArtistInfo( 4 | val biography: String, 5 | val largeImageUrl: String, 6 | val smallImageUrl: String, 7 | val mediumImageUrl: String, 8 | ) -------------------------------------------------------------------------------- /src/Components/PlaylistEntry.scss: -------------------------------------------------------------------------------- 1 | .playlist-item{ 2 | img{ 3 | width: 50px; 4 | height: 50px; 5 | border-radius: 5px; 6 | } 7 | 8 | } 9 | 10 | .rotating{ 11 | animation:"animation: rotation 2s infinite linear;" 12 | 13 | } -------------------------------------------------------------------------------- /capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | const config: CapacitorConfig = { 4 | appId: 'tech.logica10.soniclair', 5 | appName: 'SonicLair', 6 | webDir: 'build', 7 | bundledWebRuntime: false 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/Components/Account.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .account-icon-container{ 4 | padding:10px; 5 | margin-top:15px; 6 | border-radius: 10px; 7 | background: $items-color-hover; 8 | } 9 | 10 | .logout-button-container{ 11 | margin-top: 15px; 12 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SearchResult.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package tech.logica10.soniclair.models 4 | 5 | 6 | class SearchResult( 7 | val album: List?, 8 | val artist: List?, 9 | val song: List? 10 | ) 11 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/CurrentState.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import tech.logica10.soniclair.models.Song 4 | 5 | class CurrentState( 6 | val playing: Boolean, 7 | val position: Float, 8 | val currentTrack: Song, 9 | val shuffling: Boolean 10 | ) -------------------------------------------------------------------------------- /src/Models/API/Responses/IAlbumsResponse.tsx: -------------------------------------------------------------------------------- 1 | import { ISubsonicResponse } from "./SubsonicResponse"; 2 | import { IAlbumArtistResponse } from "./IArtistResponse"; 3 | 4 | export interface IAlbumsResponse extends ISubsonicResponse { 5 | albumList2: { 6 | album: IAlbumArtistResponse[]; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | fn main() { 7 | tauri::Builder::default() 8 | .run(tauri::generate_context!()) 9 | .expect("error while running tauri application"); 10 | } 11 | -------------------------------------------------------------------------------- /src/Models/API/Responses/IAlbumInfoResponse.tsx: -------------------------------------------------------------------------------- 1 | import { ISubsonicResponse } from "./SubsonicResponse"; 2 | 3 | 4 | export interface IAlbumInfoResponse extends ISubsonicResponse { 5 | albumInfo: { 6 | notes: string; 7 | musicBrainzId: string; 8 | lastFmUrl: string; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/SubsonicResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | open class SubsonicResponse { 4 | val serverVersion: String = "" 5 | val status: String = "" 6 | val type: String? = "" 7 | val version: String = "" 8 | val error: SubsonicError? = null 9 | } -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_pause_icon.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/Models/API/Responses/IArtistsResponse.tsx: -------------------------------------------------------------------------------- 1 | import { ISubsonicResponse } from "./SubsonicResponse"; 2 | import { IArtistIndex } from "./Index"; 3 | 4 | export interface IArtistsResponse extends ISubsonicResponse { 5 | artists: { 6 | ignoredArticles: string; 7 | index: IArtistIndex[]; 8 | }; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Soniclair", 3 | "name": "Soniclair", 4 | "icons": [ 5 | { 6 | "src": "favicon.svg", 7 | "sizes": "any", 8 | "purpose": "maskable any" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#626c80", 14 | "background_color": "#282c34" 15 | } -------------------------------------------------------------------------------- /android/app/src/main/res/color/background_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/round_outline_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Styles/colors.scss: -------------------------------------------------------------------------------- 1 | $items-color-hover:#626c80; 2 | $items-color-highlight:#1d2027; 3 | $items-color:#454c5a; 4 | $item-background-translucid: #00000090; 5 | $color-text-primary: #ebebeb; 6 | $color-background-app: #282c34; 7 | $color-background-app-translucid: #282c3480; 8 | $color-background-darker: #111317; 9 | 10 | $red-state:#cf1141; 11 | $red-state-highlight:#f5144d; 12 | -------------------------------------------------------------------------------- /src/Components/DraggableList.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .cancel{ 4 | position:absolute; 5 | width: 30vw; 6 | height: 10vh; 7 | background-color: $red-state; 8 | z-index: 10000000000000000000; 9 | transition: 0.5s ease; 10 | border-radius: 5px; 11 | } 12 | 13 | .cancel.cancel-hover{ 14 | background-color: $red-state-highlight; 15 | 16 | } -------------------------------------------------------------------------------- /src/Models/API/Requests/BasicParams.tsx: -------------------------------------------------------------------------------- 1 | export interface IBasicParams { 2 | u: string; // Username 3 | t: string | undefined; // Token (md5(password+salt)) 4 | s: string | undefined; // Salt 5 | v: string; // Api version targeted by client 6 | c: string; // Client identifier 7 | f: string; // Format expected (XML or Json) 8 | p: string | undefined; // Plaintext password 9 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/Models/AppContext.tsx: -------------------------------------------------------------------------------- 1 | export interface IAppContext { 2 | activeAccount: IAccount; 3 | accounts: IAccount[]; 4 | spotifyToken: string; 5 | } 6 | 7 | export interface IAccount { 8 | username: string | null; 9 | password: string; 10 | url: string; 11 | type: string; 12 | usePlaintext: boolean 13 | } 14 | 15 | export interface IAudioContext { 16 | audio: HTMLAudioElement; 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_skip_back.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_skip_forward.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_smartphone_line.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_user.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/Components/CardContextMenu.tsx: -------------------------------------------------------------------------------- 1 | import classnames from "classnames"; 2 | import { IMenuContext } from "../AppContext"; 3 | import "./CardContextMenu.scss"; 4 | 5 | export default function CardContextMenu({ body, show, x, y }: IMenuContext) { 6 | return (
9 | {body} 10 | 11 |
) 12 | } -------------------------------------------------------------------------------- /src/Components/AudioControl.scss: -------------------------------------------------------------------------------- 1 | .current-track-header { 2 | height: 10vh; 3 | overflow: hidden; 4 | margin: 0 0 0.5rem 0; 5 | 6 | .current-track-img { 7 | max-height: 10vh; 8 | height: auto; 9 | width: auto; 10 | margin: 0 0.5rem 0 0; 11 | } 12 | } 13 | 14 | .fade-right { 15 | // 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%); 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/extensions/Extensions.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.extensions 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | import tech.logica10.soniclair.R 6 | 7 | fun ImageView.loadUrl(url: String) { 8 | try { 9 | Glide.with(context).load(url).into(this) 10 | } catch (e: Exception) { 11 | Glide.with(context).load(R.drawable.ic_album_art_placeholder).into(this) 12 | } 13 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_disc.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_play_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | background-color: #282c34 !important; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_speed.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_rewind.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/Hooks/useWindowSize.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useState } from "react"; 2 | 3 | export default function useWindowSize() { 4 | const [size, setSize] = useState([0, 0]); 5 | useLayoutEffect(() => { 6 | function updateSize() { 7 | setSize([window.innerWidth, window.innerHeight]); 8 | } 9 | window.addEventListener("resize", updateSize); 10 | updateSize(); 11 | return () => window.removeEventListener("resize", updateSize); 12 | }, []); 13 | return size; 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_play_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/NotificationBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.util.Log 7 | 8 | class NotificationBroadcastReceiver : BroadcastReceiver() { 9 | override fun onReceive(context: Context, intent: Intent) { 10 | Log.i("SonicLair", "Recevied intent with action " + intent.action) 11 | Globals.NotifyObservers(intent.action, null) 12 | } 13 | } -------------------------------------------------------------------------------- /src/Components/Artists.scss: -------------------------------------------------------------------------------- 1 | .artist-list-container{ 2 | height: 100%; 3 | overflow: hidden; 4 | align-items: start; 5 | position: relative; 6 | .grid-list-container { 7 | width:100%; 8 | overflow: auto; 9 | } 10 | } 11 | .grid-list { 12 | display: grid; 13 | grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); 14 | grid-gap: 15px; 15 | // @media (max-width: 576px) { 16 | // grid-gap: 0; 17 | // } 18 | .list-group-item { 19 | margin: 0 auto; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Account.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | import tech.logica10.soniclair.TvLoginActivity 4 | 5 | class Account( 6 | val username: String?, 7 | val password: String, 8 | val url: String, 9 | var type: String, 10 | var usePlaintext: Boolean 11 | ) { 12 | constructor(formdata: TvLoginActivity.FormData) : this( 13 | formdata.username, 14 | formdata.password, 15 | formdata.url, 16 | "", 17 | formdata.plaintext 18 | ) 19 | } 20 | 21 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_shuffle_fill.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_shuffle_fill_primary.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 25 3 | compileSdkVersion = 31 4 | targetSdkVersion = 31 5 | androidxActivityVersion = '1.2.0' 6 | androidxAppCompatVersion = '1.4.2' 7 | androidxCoordinatorLayoutVersion = '1.1.0' 8 | androidxCoreVersion = '1.3.2' 9 | androidxFragmentVersion = '1.3.0' 10 | junitVersion = '4.13.2' 11 | androidxJunitVersion = '1.1.3' 12 | androidxEspressoCoreVersion = '3.4.0' 13 | cordovaAndroidVersion = '10.1.1' 14 | androidCoreKxtVersion = '1.8.0' 15 | roomVersion = '2.4.2' 16 | kspVersion = '1.5.31' 17 | ktorVersion = '2.0.2' 18 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Artist.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Ignore 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity 8 | class Artist( 9 | @PrimaryKey(autoGenerate = false) 10 | var id: String, 11 | var name: String, 12 | @Ignore 13 | var coverArt: String = "", 14 | var albumCount: Int, 15 | @Ignore 16 | var album: List = listOf() 17 | ){ 18 | constructor(id: String, 19 | name: String, 20 | albumCount: Int):this(id, name, "", albumCount, listOf()) 21 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/Constants.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | class Constants { 4 | companion object { 5 | const val SERVICE_PLAY_PAUSE = "PLAY_PAUSE" 6 | const val SERVICE_NEXT = "NEXT" 7 | const val SERVICE_PREV = "PREV" 8 | const val SERVICE_PLAY_ALBUM = "PLAY_ALBUM" 9 | const val SERVICE_PLAY_RADIO = "PLAY_RADIO" 10 | const val SERVICE_PLAY_PLAYLIST = "PLAY_PLAYLIST" 11 | const val SERVICE_PLAY_SEARCH = "PLAY_SEARCH" 12 | const val SERVICE_PLAY_SEARCH_ALBUM = "PLAY_SEARCH_ALBUM" 13 | const val SERVICE_PLAY_SEARCH_ARTIST = "PLAY_SEARCH_ARTIST" 14 | } 15 | } -------------------------------------------------------------------------------- /src/Models/API/Responses/IPlaylistsResponse.tsx: -------------------------------------------------------------------------------- 1 | import { IAlbumSongResponse } from "./IArtistResponse"; 2 | import { ISubsonicResponse } from "./SubsonicResponse"; 3 | 4 | export interface IPlaylistsResponse extends ISubsonicResponse { 5 | playlists: { playlist: IPlaylist[] }; 6 | } 7 | 8 | export interface IPlaylist { 9 | id: string; 10 | name: string; 11 | comment: string; 12 | owner: string; 13 | public: boolean; 14 | songCount: number; 15 | duration: number; 16 | created: string; 17 | coverArt: string; 18 | entry: IAlbumSongResponse[]; 19 | } 20 | 21 | export interface IPlaylistResponse extends ISubsonicResponse { 22 | playlist: IPlaylist; 23 | } 24 | -------------------------------------------------------------------------------- /src/Models/API/Responses/ISpotifyResponse.tsx: -------------------------------------------------------------------------------- 1 | export interface ISpotifyArtistsSearch { 2 | artists: { 3 | href: string, 4 | items: ISpotifyArtistItem[] 5 | } 6 | } 7 | 8 | export interface ISpotifyArtistItem { 9 | external_urls: { 10 | spotify: string 11 | }, 12 | followers: { 13 | href?: string, 14 | total: number 15 | }, 16 | genres: string[], 17 | href: string, 18 | id: string, 19 | images: ISpotifyImage[], 20 | name: string, 21 | popularity: number, 22 | type: string, 23 | uri: string 24 | } 25 | 26 | export interface ISpotifyImage { 27 | height: number, 28 | url: string, 29 | width: number 30 | } 31 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/AlbumWithSongs.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class AlbumWithSongs( 4 | var song: List, id: String, name: String, coverArt: String, 5 | songCount: Int, created: String, duration: Int, artist: String, artistId: String, year: Int 6 | ) : Album( 7 | id, name, 8 | coverArt, songCount, created, duration, artist, artistId, year 9 | ) { 10 | constructor(album: Album) : this( 11 | listOf(), 12 | album.id, 13 | album.name, 14 | album.coverArt, 15 | album.songCount, 16 | album.created, 17 | album.duration, 18 | album.artist, 19 | album.artistId, 20 | album.year 21 | ) 22 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/room/daos/ArtistDao.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.room.daos 2 | 3 | import androidx.room.* 4 | import tech.logica10.soniclair.models.Artist 5 | 6 | @Dao 7 | interface ArtistDao { 8 | @Query("SELECT * from Artist") 9 | fun getAll(): List 10 | 11 | @Query("SELECT * from Artist WHERE id = :id") 12 | fun get(id: String): Artist? 13 | 14 | @Update 15 | fun update(song: Artist) 16 | 17 | @Update 18 | fun update(songs: List) 19 | 20 | @Delete 21 | fun delete(song: Artist) 22 | 23 | @Delete 24 | fun delete(songs: List) 25 | 26 | @Insert 27 | fun insert(song: Artist) 28 | 29 | @Insert 30 | fun insert(songs: List) 31 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/BasicParams.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | class BasicParams( 4 | val u: String, 5 | val t: String?, 6 | val s: String?, 7 | val v: String, 8 | val c: String, 9 | val f: String, 10 | val p: String?, 11 | ) { 12 | fun asMap(): HashMap { 13 | val ret = HashMap() 14 | ret["u"] = u 15 | if (t != null) { 16 | ret["t"] = t 17 | } 18 | if (s != null) { 19 | ret["s"] = s 20 | } 21 | if (p != null) { 22 | ret["p"] = p 23 | } 24 | ret["v"] = v 25 | ret["c"] = c 26 | ret["f"] = f 27 | return ret 28 | } 29 | } -------------------------------------------------------------------------------- /src/Models/API/Responses/IArtistInfoResponse.tsx: -------------------------------------------------------------------------------- 1 | import { IArtist } from "./IArtist"; 2 | import { IAlbumArtistResponse, IAlbumSongResponse } from "./IArtistResponse"; 3 | import { ISubsonicResponse } from "./SubsonicResponse"; 4 | 5 | export interface IArtistInfoResponse extends ISubsonicResponse { 6 | artistInfo2: IArtistInfo; 7 | } 8 | 9 | export interface IArtistInfo { 10 | biography: string; 11 | largeImageUrl: string; 12 | smallImageUrl: string; 13 | mediumImageUrl: string; 14 | } 15 | 16 | export interface ISearchResponse extends ISubsonicResponse { 17 | searchResult3: ISearchResult; 18 | } 19 | 20 | export interface ISearchResult { 21 | album?: IAlbumArtistResponse[]; 22 | artist?: IArtist[]; 23 | song?: IAlbumSongResponse[]; 24 | } 25 | -------------------------------------------------------------------------------- /android/app/src/main/assets/capacitor.plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pkg": "@capacitor-community/barcode-scanner", 4 | "classpath": "com.getcapacitor.community.barcodescanner.BarcodeScanner" 5 | }, 6 | { 7 | "pkg": "@capacitor/app", 8 | "classpath": "com.capacitorjs.plugins.app.AppPlugin" 9 | }, 10 | { 11 | "pkg": "@capacitor/haptics", 12 | "classpath": "com.capacitorjs.plugins.haptics.HapticsPlugin" 13 | }, 14 | { 15 | "pkg": "@capacitor/keyboard", 16 | "classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin" 17 | }, 18 | { 19 | "pkg": "@capacitor/status-bar", 20 | "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin" 21 | }, 22 | { 23 | "pkg": "@capacitor/toast", 24 | "classpath": "com.capacitorjs.plugins.toast.ToastPlugin" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/room/database/SoniclairDatabase.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.room.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import tech.logica10.soniclair.models.Album 6 | import tech.logica10.soniclair.models.Artist 7 | import tech.logica10.soniclair.models.Song 8 | import tech.logica10.soniclair.room.daos.AlbumDao 9 | import tech.logica10.soniclair.room.daos.ArtistDao 10 | import tech.logica10.soniclair.room.daos.SongDao 11 | 12 | @Database(entities = [Song::class, Album::class, Artist::class], version = 2, exportSchema = false) 13 | abstract class SoniclairDatabase: RoomDatabase() { 14 | abstract fun songDao(): SongDao; 15 | abstract fun albumDao(): AlbumDao; 16 | abstract fun artistDao(): ArtistDao; 17 | } -------------------------------------------------------------------------------- /android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_1_8 6 | targetCompatibility JavaVersion.VERSION_1_8 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | implementation project(':capacitor-community-barcode-scanner') 13 | implementation project(':capacitor-app') 14 | implementation project(':capacitor-haptics') 15 | implementation project(':capacitor-keyboard') 16 | implementation project(':capacitor-status-bar') 17 | implementation project(':capacitor-toast') 18 | 19 | } 20 | 21 | 22 | if (hasProperty('postBuildExtras')) { 23 | postBuildExtras() 24 | } 25 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/room/daos/AlbumDao.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.room.daos 2 | 3 | import androidx.room.* 4 | import tech.logica10.soniclair.models.Album 5 | 6 | @Dao 7 | interface AlbumDao { 8 | @Query("SELECT * from Album") 9 | fun getAll(): List 10 | 11 | @Query("SELECT * from Album WHERE id = :id") 12 | fun get(id: String): Album? 13 | 14 | @Query("SELECT * from Album WHERE artistId = :id") 15 | fun getByArtist(id: String): List 16 | 17 | @Update 18 | fun update(song: Album) 19 | 20 | @Update 21 | fun update(songs: List) 22 | 23 | @Delete 24 | fun delete(song: Album) 25 | 26 | @Delete 27 | fun delete(songs: List) 28 | 29 | @Insert 30 | fun insert(song: Album) 31 | 32 | @Insert 33 | fun insert(songs: List) 34 | } -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/room/daos/SongDao.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.room.daos 2 | 3 | import androidx.room.* 4 | import tech.logica10.soniclair.models.Song 5 | 6 | @Dao 7 | interface SongDao { 8 | @Query("SELECT * from Song") 9 | fun getAll(): List 10 | 11 | @Query("SELECT * from Song WHERE id = :id") 12 | fun get(id: String): Song? 13 | 14 | @Query("SELECT * from Song WHERE albumId = :id") 15 | fun getByAlbum(id: String): List 16 | 17 | @Update 18 | fun update(song: Song) 19 | 20 | @Update 21 | fun update(songs: List) 22 | 23 | @Delete 24 | fun delete(song: Song) 25 | 26 | @Delete 27 | fun delete(songs: List) 28 | 29 | @Insert 30 | fun insert(song:Song) 31 | 32 | @Insert 33 | fun insert(songs: List) 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Components/Loading.scss: -------------------------------------------------------------------------------- 1 | .lds-facebook { 2 | display: inline-block; 3 | position: relative; 4 | width: 80px; 5 | height: 80px; 6 | } 7 | .lds-facebook div { 8 | display: inline-block; 9 | position: absolute; 10 | left: 8px; 11 | width: 16px; 12 | background: #fff; 13 | animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite; 14 | } 15 | .lds-facebook div:nth-child(1) { 16 | left: 8px; 17 | animation-delay: -0.24s; 18 | } 19 | .lds-facebook div:nth-child(2) { 20 | left: 32px; 21 | animation-delay: -0.12s; 22 | } 23 | .lds-facebook div:nth-child(3) { 24 | left: 56px; 25 | animation-delay: 0; 26 | } 27 | @keyframes lds-facebook { 28 | 0% { 29 | top: 8px; 30 | height: 64px; 31 | } 32 | 50%, 100% { 33 | top: 24px; 34 | height: 32px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Components/AccountItem.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .account-item { 4 | max-width: 80vw; 5 | width: 350px; 6 | background-color: $items-color-hover; 7 | // padding: 10px; 8 | border-radius: 10px; 9 | margin: 0 0 10px 0; 10 | overflow: hidden; 11 | transition: 0.5s; 12 | user-select: none; 13 | 14 | .account-icon { 15 | margin: 10px; 16 | } 17 | 18 | .account-info { 19 | max-width: 70vw; 20 | width: 100%; 21 | overflow: hidden; 22 | margin: 10px; 23 | } 24 | 25 | .delete { 26 | background-color: #dc3545; 27 | padding: 20px; 28 | height: 100%; 29 | transition: 0.5s; 30 | } 31 | 32 | .delete.del-focused { 33 | transform: scale(1.1); 34 | } 35 | } 36 | 37 | .account-item.account-item-focused { 38 | transform: scale(1.1); 39 | 40 | 41 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.7.0-RC' 5 | 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:7.2.1' 12 | classpath 'com.google.gms:google-services:4.3.10' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0" 14 | classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1") 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | apply from: "variables.gradle" 22 | 23 | allprojects { 24 | repositories { 25 | google() 26 | mavenCentral() 27 | } 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /src/AudioContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { SetStateAction, Dispatch } from "react"; 2 | import { IAlbumSongResponse } from "./Models/API/Responses/IArtistResponse"; 3 | 4 | 5 | 6 | export const CurrentTrackContextDefValue: IAlbumSongResponse = { 7 | duration: 0, id: "", parent: "", title: "", track: 0, artist: "", coverArt: "", album: "", albumId: "" 8 | } 9 | 10 | export const CurrentTrackContext = React.createContext< 11 | { 12 | currentTrack: IAlbumSongResponse, 13 | setCurrentTrack: Dispatch>, 14 | playing: boolean, 15 | setPlaying: Dispatch>, 16 | playtime: number, 17 | setPlaytime: Dispatch>, 18 | }> 19 | ({ 20 | currentTrack: CurrentTrackContextDefValue, 21 | setCurrentTrack: (c) => { }, 22 | playing: false, 23 | setPlaying: (c)=>{}, 24 | playtime: 0, 25 | setPlaytime: () => {}}); -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Song.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Ignore 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity 8 | class Song( 9 | @PrimaryKey(autoGenerate = false) 10 | var id: String, 11 | var parent: String, 12 | var title: String, 13 | var duration: Int, 14 | var track: Int, 15 | var artist: String, 16 | var album: String, 17 | var albumId: String, 18 | var coverArt: String, 19 | ) : ICardViewModel { 20 | override fun firstLine(): String { 21 | return title 22 | } 23 | 24 | override fun secondLine(): String { 25 | return "by $artist" 26 | } 27 | 28 | @Ignore 29 | private var _image: String = "" 30 | 31 | override var image: String 32 | get() { 33 | return _image 34 | } 35 | set(value) { 36 | _image = value 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | default-run = "app" 9 | edition = "2021" 10 | rust-version = "1.57" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "1.0.0-rc.6", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "1.0.0-rc.7", features = ["api-all"] } 21 | 22 | 23 | [features] 24 | # by default Tauri runs in production mode 25 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 26 | default = [ "custom-protocol" ] 27 | # this feature is used used for production builds where `devPath` points to the filesystem 28 | # DO NOT remove this 29 | custom-protocol = [ "tauri/custom-protocol" ] 30 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/Album.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Ignore 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity 8 | open class Album( 9 | @PrimaryKey(autoGenerate = false) 10 | var id: String, 11 | var name: String, 12 | var coverArt: String, 13 | var songCount: Int, 14 | var created: String, 15 | var duration: Int, 16 | var artist: String, 17 | var artistId: String, 18 | var year: Int 19 | ) : ICardViewModel { 20 | override fun firstLine(): String { 21 | return name; 22 | } 23 | 24 | override fun secondLine(): String { 25 | return year.toString() 26 | } 27 | 28 | @Ignore 29 | private var _image: String = ""; 30 | 31 | override var image: String 32 | get() { 33 | return _image 34 | } 35 | set(value) { 36 | _image = value 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_playlists.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /src/Components/Artist.scss: -------------------------------------------------------------------------------- 1 | .artist-container { 2 | height: 100%; 3 | overflow: hidden; 4 | align-items: start; 5 | position: relative; 6 | border-radius: 2%; 7 | .grid-list-container { 8 | width:100%; 9 | overflow: auto; 10 | } 11 | .artist-name-container { 12 | z-index: 1000; 13 | width: 100%; 14 | font-size: 3rem; 15 | padding: 10px; 16 | text-align: left; 17 | background: rgb(2, 0, 36,0.4); 18 | background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.7)100%); 19 | } 20 | .artist-image-container{ 21 | position: absolute; 22 | height: 100%; 23 | width:100%; 24 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(40, 44, 52, 1)); 25 | z-index:-1; 26 | } 27 | .artist-img { 28 | position: absolute; 29 | text-align: center; 30 | z-index:-2; 31 | mask-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(40, 44, 52, 1)); 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 CARLOS PEREZ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/Api/GetSpotifyToken.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import qs from "qs"; 3 | 4 | export interface ISpotifyToken { 5 | token: string; 6 | } 7 | 8 | export default async function GetSpotifyToken(): Promise { 9 | var client_id = "3cb3ecad8ce14e1dba560e3b5ceb908b"; 10 | var client_secret = "86810d6f234142a9bf7be9d2a924bbba"; 11 | 12 | const headers = { 13 | headers: { 14 | Accept: "application/json", 15 | "Content-Type": "application/x-www-form-urlencoded", 16 | }, 17 | auth: { 18 | username: client_id, 19 | password: client_secret, 20 | }, 21 | }; 22 | const data = { 23 | grant_type: "client_credentials", 24 | }; 25 | 26 | try { 27 | const response = await axios.post( 28 | "https://accounts.spotify.com/api/token", 29 | qs.stringify(data), 30 | headers 31 | ); 32 | return response.data.access_token; 33 | } catch (error) { 34 | throw error; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #282c34 4 | #626c80 5 | 6 | 12 | 13 | 18 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/Plugins/AndroidTV.tsx: -------------------------------------------------------------------------------- 1 | import { Plugin, registerPlugin, WebPlugin } from "@capacitor/core"; 2 | 3 | export interface IAndroidTVResponse { 4 | value: boolean; 5 | } 6 | 7 | export interface IAndroidTVStringResponse { 8 | value: string; 9 | } 10 | 11 | export interface IAndroidTVPlugin extends Plugin { 12 | get(): Promise; 13 | getIp(): Promise; 14 | } 15 | 16 | class AndroidTV extends WebPlugin implements IAndroidTVPlugin { 17 | get(): Promise { 18 | const ret = 19 | window.navigator.userAgent.indexOf("Tizen") > -1 || 20 | window.navigator.userAgent.indexOf("WebOS") > -1 || 21 | window.navigator.userAgent.indexOf("Xbox") > -1; 22 | return Promise.resolve({ value: ret }); 23 | } 24 | getIp(): Promise { 25 | return Promise.resolve({ value: "127.0.0.1" }); 26 | } 27 | } 28 | 29 | const AndroidTVPlugin = registerPlugin("AndroidTV", { 30 | web: () => new AndroidTV(), 31 | }); 32 | 33 | export default AndroidTVPlugin; 34 | -------------------------------------------------------------------------------- /android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | 5 | include ':capacitor-community-barcode-scanner' 6 | project(':capacitor-community-barcode-scanner').projectDir = new File('../node_modules/@capacitor-community/barcode-scanner/android') 7 | 8 | include ':capacitor-app' 9 | project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android') 10 | 11 | include ':capacitor-haptics' 12 | project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android') 13 | 14 | include ':capacitor-keyboard' 15 | project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android') 16 | 17 | include ':capacitor-status-bar' 18 | project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android') 19 | 20 | include ':capacitor-toast' 21 | project(':capacitor-toast').projectDir = new File('../node_modules/@capacitor/toast/android') 22 | -------------------------------------------------------------------------------- /src/Components/ArtistCard.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .artist-item-focused { 4 | background-color: $items-color-hover !important; 5 | } 6 | 7 | .artist-item { 8 | transition: 0.3s; 9 | background-color: $item-background-translucid; 10 | // background-color: $items-color !important; 11 | border-radius: 5% !important; 12 | // max-width:170px; 13 | max-width:170px; 14 | width: calc(100% - 20px); 15 | // min-height: calc(190px + 2.5em); 16 | height: calc(100% - 20px); 17 | margin: 10px; 18 | padding:10px !important; 19 | .artist-image-container{ 20 | overflow:hidden; 21 | img { 22 | max-width: 100%; 23 | max-height:100%; 24 | 25 | width:auto; 26 | height:auto; 27 | display:block; 28 | border-radius: 5%; 29 | } 30 | } 31 | 32 | &:hover{ 33 | background-color: $items-color-hover !important; 34 | } 35 | .no-overflow{ 36 | width:150px; 37 | height: 3.5rem; 38 | overflow:hidden; 39 | white-space: nowrap; 40 | text-overflow: ellipsis; 41 | } 42 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/models/AlbumResponse.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.models 2 | 3 | import androidx.room.Ignore 4 | 5 | class AlbumResponse(val album: AlbumWithSongs) : SubsonicResponse() 6 | 7 | class PlaylistsResponse(val playlists: PlaylistsInnerResponse) : SubsonicResponse() 8 | 9 | class PlaylistsInnerResponse(val playlist: List) 10 | 11 | class PlaylistResponse(val playlist: Playlist) : SubsonicResponse() 12 | 13 | class Playlist( 14 | val id: String, 15 | val name: String, 16 | val comment: String?, 17 | val owner: String, 18 | val public: Boolean, 19 | val songCount: Int, 20 | val duration: Int, 21 | val created: String, 22 | val coverArt: String?, 23 | var entry: List 24 | ) : ICardViewModel { 25 | override fun firstLine(): String { 26 | return name 27 | } 28 | 29 | override fun secondLine(): String { 30 | return if(comment.isNullOrBlank()) "by ${owner}" else comment 31 | } 32 | 33 | @Ignore 34 | private var _image: String = ""; 35 | 36 | override var image: String 37 | get() { 38 | return _image 39 | } 40 | set(value) { 41 | _image = value 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/Components/AlbumCard.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .album-image { 4 | margin: 10 10 10 0; 5 | } 6 | 7 | .album-item-focused { 8 | background-color: $items-color-hover !important; 9 | transform: scale(1.1); 10 | } 11 | 12 | .album-item { 13 | transition: 0.3s; 14 | background-color: $item-background-translucid ; 15 | // background-color: $items-color !important; 16 | border-radius: 5% !important; 17 | // max-width:170px; 18 | max-width: 170px; 19 | width: calc(100% - 20px); 20 | // min-height: calc(190px + 2.5em); 21 | height: calc(100% - 20px); 22 | margin: 10px; 23 | padding: 10px !important; 24 | 25 | .album-image-container { 26 | overflow: hidden; 27 | 28 | img { 29 | max-width: 100%; 30 | max-height: 100%; 31 | 32 | width: auto; 33 | height: auto; 34 | display: block; 35 | border-radius: 5%; 36 | } 37 | } 38 | 39 | &:hover { 40 | background-color: $items-color-hover !important; 41 | } 42 | 43 | .no-overflow { 44 | width: 150px; 45 | height: 3.5rem; 46 | overflow: hidden; 47 | white-space: nowrap; 48 | text-overflow: ellipsis; 49 | } 50 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { BrowserRouter } from "react-router-dom"; 7 | import { defineCustomElements } from '@ionic/pwa-elements/loader'; 8 | 9 | const root = ReactDOM.createRoot( 10 | document.getElementById('root') as HTMLElement 11 | ); 12 | root.render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | // If you want to start measuring performance in your app, pass a function 23 | // to log results (for example: reportWebVitals(console.log)) 24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 25 | reportWebVitals(); 26 | defineCustomElements(window); 27 | -------------------------------------------------------------------------------- /android/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /src/AppContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { SetStateAction, Dispatch } from "react"; 2 | import { IAccount } from "./Models/AppContext"; 3 | 4 | export const AppContextDefValue: IAccount = { 5 | username: "", 6 | password: "", 7 | url: "", 8 | type: "", 9 | usePlaintext: false, 10 | }; 11 | 12 | export const AppContext = React.createContext<{ context: IAccount, setContext: Dispatch> }> 13 | ({ context: AppContextDefValue, setContext: (c) => { } }); 14 | 15 | export interface IMenuContext { 16 | x: number | string; 17 | y: number | string; 18 | show: boolean; 19 | body: any; 20 | } 21 | export const MenuContextDefValue: IMenuContext = { 22 | x: 0, 23 | y: 0, 24 | show: false, 25 | body: <> 26 | 27 | } 28 | 29 | export const MenuContext = React.createContext<{ menuContext: IMenuContext, setMenuContext: Dispatch> }> 30 | ({ menuContext: MenuContextDefValue, setMenuContext: (c) => { } }); 31 | 32 | export interface IStateContext { 33 | selectedAlbum: number[]; 34 | selectedArtist: number[]; 35 | } 36 | 37 | export const StateContextDefValue: IStateContext = { 38 | selectedAlbum: [0,0], 39 | selectedArtist: [0,0], 40 | } 41 | 42 | export const StateContext = React.createContext<{ stateContext: IStateContext, setStateContext: Dispatch> }> 43 | ({ stateContext: StateContextDefValue, setStateContext: () => { } }); -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/Helpers.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import com.google.zxing.BarcodeFormat 6 | import com.google.zxing.common.BitMatrix 7 | import com.google.zxing.qrcode.QRCodeWriter 8 | 9 | class Helpers { 10 | companion object{ 11 | fun constructPath(paths: List) :String{ 12 | val builder = StringBuilder(); 13 | paths.forEach{ 14 | builder.append(it.trimEnd('/')) 15 | builder.append("/") 16 | } 17 | return builder.toString().trimEnd('/') 18 | } 19 | 20 | fun encodeAsBitmap(str: String?): Bitmap? { 21 | val writer = QRCodeWriter() 22 | val bitMatrix: BitMatrix = writer.encode(str, BarcodeFormat.QR_CODE, 800, 800) 23 | val w: Int = bitMatrix.width 24 | val h: Int = bitMatrix.height 25 | val pixels = IntArray(w * h) 26 | for (y in 0 until h) { 27 | for (x in 0 until w) { 28 | pixels[y * w + x] = 29 | if (bitMatrix.get(x, y)) Color.parseColor("#282c34") else Color.WHITE 30 | } 31 | } 32 | val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) 33 | bitmap.setPixels(pixels, 0, w, 0, 0, w, h) 34 | return bitmap 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Components/Sidebar.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .sidebar { 4 | width: 5rem !important; 5 | background-color: $items-color-highlight !important; 6 | height: 80vh; 7 | position: absolute; 8 | left: calc(100vw - 5.3rem); 9 | top: 0; 10 | transition: ease 0.3s; 11 | margin: 10vh 0 0 0; 12 | border-radius: 10px; 13 | z-index:3000; 14 | .sidebar-item { 15 | width: 4rem; 16 | height: 4rem; 17 | background-color: $items-color; 18 | border-radius: 10%; 19 | margin-top: 0.5rem; 20 | transition: ease 0.3s; 21 | 22 | &:hover { 23 | background-color: $items-color-hover; 24 | } 25 | 26 | svg { 27 | font-size: 2.5rem; 28 | } 29 | } 30 | .last-item{ 31 | margin-top:auto; 32 | margin-bottom: 0.5rem; 33 | } 34 | 35 | .sidebar-item-borderless { 36 | width: 4rem; 37 | height: 4rem; 38 | border-radius: 10%; 39 | margin-bottom: 0.5rem; 40 | transition: ease 0.3s; 41 | 42 | &:hover { 43 | background-color: $items-color-hover; 44 | } 45 | 46 | svg { 47 | font-size: 2.5rem; 48 | } 49 | } 50 | } 51 | 52 | .modal-cover{ 53 | position:fixed; 54 | left:0; 55 | top:0; 56 | background-color: $item-background-translucid; 57 | width:100vw; 58 | height:100vh; 59 | z-index: 2000; 60 | transition: ease 0.1s; 61 | } -------------------------------------------------------------------------------- /src/Helpers.ts: -------------------------------------------------------------------------------- 1 | import { IAlbumSongResponse } from "./Models/API/Responses/IArtistResponse"; 2 | 3 | export function GetAsParams(data: any): string { 4 | return GetAsUrlParams(data).toString(); 5 | } 6 | 7 | export function GetAsUrlParams(data: any): URLSearchParams { 8 | let params = new URLSearchParams(data); 9 | let keysForDel: string[] = []; 10 | params.forEach((value, key) => { 11 | if (value === "undefined") { 12 | keysForDel.push(key); 13 | } 14 | }); 15 | 16 | keysForDel.forEach((key) => { 17 | params.delete(key); 18 | }); 19 | return params; 20 | } 21 | 22 | export function SecondsToHHSS(data: number) { 23 | if (data >= 3600) { 24 | return `${Math.floor(data / 3600) 25 | .toString() 26 | .padStart(2, "0")}:${Math.floor((data % 3600) / 60) 27 | .toString() 28 | .padStart(2, "0")}:${((data % 3600) % 60) 29 | .toString() 30 | .padStart(2, "0")}`; 31 | } else { 32 | return `${Math.floor(data / 60) 33 | .toString() 34 | .padStart(2, "0")}:${Math.floor(data % 60) 35 | .toString() 36 | .padStart(2, "0")}`; 37 | } 38 | } 39 | 40 | export function onlyUnique(value: any, index: any, self: any) { 41 | return self.indexOf(value) === index; 42 | } 43 | 44 | export function getPlaylistDuration(playlist: IAlbumSongResponse[]) { 45 | return playlist.reduce((prev, curr) => { 46 | return prev + curr.duration; 47 | }, 0); 48 | } 49 | -------------------------------------------------------------------------------- /src/Models/API/Responses/IArtistResponse.tsx: -------------------------------------------------------------------------------- 1 | import { ISubsonicResponse } from "./SubsonicResponse"; 2 | 3 | export interface IArtistResponse extends ISubsonicResponse { 4 | artist: IInnerArtistResponse 5 | } 6 | 7 | export interface IInnerArtistResponse { 8 | id: string; 9 | name: string; 10 | coverArt: string; 11 | albumCount: number; 12 | album: IAlbumArtistResponse[]; 13 | } 14 | 15 | export interface IRandomSongsResponse extends ISubsonicResponse { 16 | randomSongs: { song: IAlbumSongResponse[] }; 17 | } 18 | 19 | export interface ISimilarSongsResponse extends ISubsonicResponse { 20 | similarSongs2: { song: IAlbumSongResponse[] }; 21 | } 22 | 23 | export interface IAlbumArtistResponse { 24 | id: string; 25 | name: string; 26 | coverArt: string; 27 | songCount: number; 28 | created: string; 29 | duration: number; 30 | artist: string; 31 | artistId: string; 32 | year: number; 33 | } 34 | 35 | export interface IAlbumResponse extends ISubsonicResponse { 36 | album: IInnerAlbumResponse; 37 | } 38 | 39 | export interface IInnerAlbumResponse extends IAlbumArtistResponse { 40 | song: IAlbumSongResponse[]; 41 | } 42 | 43 | export interface ISongResponse extends ISubsonicResponse{ 44 | song: IAlbumSongResponse; 45 | } 46 | 47 | export interface IAlbumSongResponse { 48 | id: string; 49 | parent: string; 50 | title: string; 51 | duration: number; 52 | track: number; 53 | artist: string; 54 | album: string; 55 | albumId: string; 56 | coverArt: string; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/fragments/JukeboxFragment.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair.fragments 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ImageView 10 | import android.widget.TextView 11 | import androidx.fragment.app.Fragment 12 | import com.google.zxing.BarcodeFormat 13 | import com.google.zxing.common.BitMatrix 14 | import com.google.zxing.qrcode.QRCodeWriter 15 | import tech.logica10.soniclair.App 16 | import tech.logica10.soniclair.Helpers.Companion.encodeAsBitmap 17 | import tech.logica10.soniclair.R 18 | 19 | 20 | class JukeboxFragment : Fragment() { 21 | 22 | 23 | override fun onCreateView( 24 | inflater: LayoutInflater, container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View? { 27 | // Inflate the layout for this fragment 28 | return inflater.inflate(R.layout.fragment_jukebox, container, false) 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | 34 | val image: ImageView = view.findViewById(R.id.iv_jukebox_qr) 35 | val text: TextView = view.findViewById(R.id.tv_jukebox_ip) 36 | val ip = if (App.localIp == null) "127.0.0.1" else "${App.localIp}j" 37 | val bitmap = encodeAsBitmap(ip) 38 | image.setImageBitmap(bitmap) 39 | text.text = ip.trimEnd('j') 40 | } 41 | } -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_jukebox.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 29 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "productName": "soniclair-tauri", 4 | "version": "0.1.0" 5 | }, 6 | "build": { 7 | "distDir": "../build", 8 | "devPath": "http://localhost:3000", 9 | "beforeDevCommand": "npm run start", 10 | "beforeBuildCommand": "npm run build" 11 | }, 12 | "tauri": { 13 | "bundle": { 14 | "active": true, 15 | "targets": "all", 16 | "identifier": "com.tauri.dev", 17 | "icon": [ 18 | "icons/32x32.png", 19 | "icons/128x128.png", 20 | "icons/128x128@2x.png", 21 | "icons/icon.icns", 22 | "icons/icon.ico" 23 | ], 24 | "resources": [], 25 | "externalBin": [], 26 | "copyright": "", 27 | "category": "DeveloperTool", 28 | "shortDescription": "", 29 | "longDescription": "", 30 | "deb": { 31 | "depends": [] 32 | }, 33 | "macOS": { 34 | "frameworks": [], 35 | "exceptionDomain": "", 36 | "signingIdentity": null, 37 | "providerShortName": null, 38 | "entitlements": null 39 | }, 40 | "windows": { 41 | "certificateThumbprint": null, 42 | "digestAlgorithm": "sha256", 43 | "timestampUrl": "" 44 | } 45 | }, 46 | "updater": { 47 | "active": false 48 | }, 49 | "allowlist": { 50 | "all": true 51 | }, 52 | "windows": [ 53 | { 54 | "title": "SonicLair", 55 | "width": 800, 56 | "height": 600, 57 | "resizable": true, 58 | "fullscreen": false 59 | } 60 | ], 61 | "security": { 62 | "csp": null 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Components/TVJukebox.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { QRCode } from "react-qrcode-logo"; 3 | import AndroidTVPlugin from "../Plugins/AndroidTV"; 4 | 5 | export default function TVJukebox() { 6 | const [localIp, setLocalIp] = useState(""); 7 | useEffect(() => { 8 | const fetch = async () => { 9 | const androidTv = (await AndroidTVPlugin.get()).value; 10 | if (androidTv) { 11 | setLocalIp((await AndroidTVPlugin.getIp()).value); 12 | } 13 | }; 14 | fetch(); 15 | }, []); 16 | 17 | return ( 18 |
19 |
20 | Connect your phone's SonicLair 21 |
22 |
29 | 39 |
40 | {localIp} 41 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/Components/TVTopBar.tsx: -------------------------------------------------------------------------------- 1 | import { PluginListenerHandle } from "@capacitor/core"; 2 | import { useEffect, useRef, useState } from "react"; 3 | import { useNavigate } from "react-router-dom"; 4 | import VLC from "../Plugins/VLC"; 5 | 6 | export function TVTopBar() { 7 | const [websocketConnected, setWebsocketConnected] = 8 | useState(false); 9 | const listener = useRef(); 10 | const navigate = useNavigate(); 11 | useEffect(() => { 12 | const fetch = async () => { 13 | if (listener.current) { 14 | listener.current.remove(); 15 | } 16 | listener.current = await VLC.addListener( 17 | "webSocketConnection", 18 | (info: any) => { 19 | setWebsocketConnected(info.connected); 20 | if (info.connected) { 21 | navigate("/playing"); 22 | } 23 | } 24 | ); 25 | }; 26 | fetch(); 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, []); 29 | 30 | return ( 31 |
32 |
33 | 40 | SonicLair 41 |
42 | {websocketConnected && ( 43 | 47 | )} 48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /SonicLairAndroid.gocd.yaml: -------------------------------------------------------------------------------- 1 | format_version: 10 2 | pipelines: 3 | SonicLairAndroid: 4 | group: defaultGroup 5 | label_template: ${COUNT} 6 | lock_behavior: none 7 | display_order: -1 8 | materials: 9 | git-89737ad: 10 | git: https://github.com/thelinkin3000/SonicLair 11 | shallow_clone: false 12 | auto_update: true 13 | branch: dev 14 | stages: 15 | - Build: 16 | fetch_materials: true 17 | keep_artifacts: false 18 | clean_workspace: false 19 | approval: 20 | type: success 21 | allow_only_on_success: false 22 | jobs: 23 | Build: 24 | timeout: 0 25 | tasks: 26 | - exec: 27 | arguments: 28 | - install 29 | - --legacy-peer-deps 30 | command: npm 31 | run_if: passed 32 | - exec: 33 | arguments: 34 | - run 35 | - build 36 | command: npm 37 | run_if: passed 38 | - exec: 39 | arguments: 40 | - cap 41 | - sync 42 | - android 43 | command: npx 44 | run_if: passed 45 | - exec: 46 | arguments: 47 | - /var/go/releasekey.jks 48 | - android/app/releasekey.jks 49 | command: cp 50 | run_if: passed 51 | - script: cd android && chmod +x ./gradlew && ./gradlew :app:lintRelease &&./gradlew assembleRelease 52 | - exec: 53 | arguments: 54 | - android/app/build/outputs/apk/release/app-release.apk 55 | - /var/go/soniclair.apk 56 | command: cp 57 | run_if: passed 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/tech/logica10/soniclair/AccountFragment.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Button 10 | import android.widget.TextView 11 | import tech.logica10.soniclair.models.Account 12 | 13 | class AccountFragment() : Fragment() { 14 | private lateinit var user: TextView 15 | private lateinit var server: TextView 16 | private lateinit var type: TextView 17 | private lateinit var plaintext: TextView 18 | private lateinit var logout: Button 19 | override fun onCreateView( 20 | inflater: LayoutInflater, container: ViewGroup?, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | // Inflate the layout for this fragment 24 | return inflater.inflate(R.layout.fragment_account, container, false) 25 | } 26 | 27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 | super.onViewCreated(view, savedInstanceState) 29 | user = view.findViewById(R.id.tv_account_user) 30 | server = view.findViewById(R.id.tv_account_server) 31 | type = view.findViewById(R.id.tv_account_type) 32 | plaintext = view.findViewById(R.id.tv_plaintext_warning) 33 | logout = view.findViewById(R.id.btn_logout) 34 | val account = KeyValueStorage.getActiveAccount() 35 | user.text = account.username 36 | server.text = account.url 37 | type.text = account.type 38 | plaintext.visibility = if (account.usePlaintext) View.VISIBLE else View.INVISIBLE 39 | logout.setOnClickListener { 40 | KeyValueStorage.setActiveAccount(Account(null, "","","",false)) 41 | val intent = Intent(activity, TvLoginActivity::class.java) 42 | startActivity(intent) 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /storelogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 22 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Components/NowPlaying.scss: -------------------------------------------------------------------------------- 1 | @import "../Styles/colors.scss"; 2 | 3 | .current-track-img-tv { 4 | max-height: 30vh; 5 | max-width: 30vh; 6 | height: 500px; 7 | width: 500px; 8 | margin: 0 0.5rem 0 0; 9 | border-radius: 5%; 10 | transition: 2s; 11 | } 12 | 13 | .current-track-stopped { 14 | box-shadow: 15 | 0 0 5px 2px #fff, 16 | 0 0 7px 5px #0ff; 17 | animation: dying-pulse 2s normal forwards ease-in-out; 18 | } 19 | 20 | .current-track-playing { 21 | transition: 2s; 22 | 23 | box-shadow: 24 | 0 0 5px 2px #fff, 25 | 0 0 7px 5px #0ff; 26 | animation: pulse 2s infinite ease-in-out; 27 | } 28 | 29 | .current-track-header { 30 | overflow: hidden; 31 | margin: 0 0 0.5rem 0; 32 | 33 | 34 | } 35 | 36 | .btn-tv-selected { 37 | background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(40, 44, 52, 1)) !important; 38 | background-color: $items-color-hover !important; 39 | transition: 0.5s; 40 | 41 | } 42 | 43 | .playlist{ 44 | // height:300px; 45 | // width: 400px; 46 | max-height: 35vh; 47 | max-width: 45%; 48 | overflow: scroll; 49 | } 50 | 51 | .shadow-down { 52 | width: 100vw; 53 | height: 1px; 54 | position: absolute; 55 | left: 0; 56 | top: calc(100vh - 1px); 57 | box-shadow: 58 | 0 0 15px 10px #fff, 59 | 0 0 20px 15px #0ff, 60 | } 61 | 62 | @keyframes pulse { 63 | 0% { 64 | box-shadow: 65 | 0 0 5px 2px #fff, 66 | 0 0 7px 5px #0ff, 67 | } 68 | 69 | 25% { 70 | box-shadow: 71 | 0 0 5px 2px #0ff, 72 | 0 0 7px 5px #7df, 73 | } 74 | 75 | 50% { 76 | box-shadow: 77 | 0 0 5px 2px #7df, 78 | 0 0 7px 5px #478499, 79 | } 80 | 81 | 75% { 82 | box-shadow: 83 | 0 0 5px 2px #478499, 84 | 0 0 7px 5px #fff, 85 | } 86 | } 87 | 88 | @keyframes dying-pulse{ 89 | from { 90 | box-shadow: 91 | 0 0 5px 2px #fff, 92 | 0 0 7px 5px #0ff, 93 | } 94 | to { 95 | box-shadow: none; 96 | } 97 | } -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore 2 | 3 | # Built application files 4 | *.apk 5 | *.aar 6 | *.ap_ 7 | *.aab 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | # Uncomment the following line in case you need and you don't have the release build type files in your app 20 | # release/ 21 | 22 | # Gradle files 23 | .gradle/ 24 | build/ 25 | 26 | # Local configuration file (sdk path, etc) 27 | local.properties 28 | 29 | # Proguard folder generated by Eclipse 30 | proguard/ 31 | 32 | # Log Files 33 | *.log 34 | 35 | # Android Studio Navigation editor temp files 36 | .navigation/ 37 | 38 | # Android Studio captures folder 39 | captures/ 40 | 41 | # IntelliJ 42 | *.iml 43 | .idea/workspace.xml 44 | .idea/tasks.xml 45 | .idea/gradle.xml 46 | .idea/assetWizardSettings.xml 47 | .idea/dictionaries 48 | .idea/libraries 49 | # Android Studio 3 in .gitignore file. 50 | .idea/caches 51 | .idea/modules.xml 52 | .idea/deploymentTargetDropDown.xml 53 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 54 | .idea/navEditor.xml 55 | 56 | # Keystore files 57 | # Uncomment the following lines if you do not want to check your keystore files in. 58 | #*.jks 59 | #*.keystore 60 | 61 | # External native build folder generated in Android Studio 2.2 and later 62 | .externalNativeBuild 63 | .cxx/ 64 | 65 | # Google Services (e.g. APIs or Firebase) 66 | # google-services.json 67 | 68 | # Freeline 69 | freeline.py 70 | freeline/ 71 | freeline_project_description.json 72 | 73 | # fastlane 74 | fastlane/report.xml 75 | fastlane/Preview.html 76 | fastlane/screenshots 77 | fastlane/test_output 78 | fastlane/readme.md 79 | 80 | # Version control 81 | vcs.xml 82 | 83 | # lint 84 | lint/intermediates/ 85 | lint/generated/ 86 | lint/outputs/ 87 | lint/tmp/ 88 | # lint/reports/ 89 | 90 | # Android Profiling 91 | *.hprof 92 | 93 | # Cordova plugins for Capacitor 94 | capacitor-cordova-android-plugins 95 | 96 | # Copied web assets 97 | app/src/main/assets/public 98 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SonicLair 4 | SonicLair 5 | tech.logica10.soniclair 6 | tech.logica10.soniclair 7 | App logo 8 | Home 9 | Search 10 | Playlists 11 | Account 12 | Jukebox 13 | Playing 14 | 15 | Hello blank fragment 16 | Album card image 17 | Most Played Albums 18 | Random Songs 19 | Recently Played 20 | Recently Added 21 | Album Art Image on Playing 22 | Button 23 | Connect you phone\'s SonicLair 24 | Log in 25 | Show QR 26 | Username 27 | Password 28 | Url 29 | Search... 30 | Albums 31 | Songs 32 | Playlists 33 | Use plaintext password (insecure on http connections, needed for some servers) 34 | Using plaintext password 35 | Logout 36 | 37 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/album_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 27 | 28 | 38 | 39 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 30 | SonicLair 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/AndroidTVPlugin.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import android.app.UiModeManager 4 | import android.content.Context.UI_MODE_SERVICE 5 | import android.content.res.Configuration 6 | import com.getcapacitor.JSObject 7 | import com.getcapacitor.Plugin 8 | import com.getcapacitor.PluginCall 9 | import com.getcapacitor.PluginMethod 10 | import com.getcapacitor.annotation.CapacitorPlugin 11 | import tech.logica10.soniclair.App.Companion.localIp 12 | 13 | @CapacitorPlugin(name = "AndroidTV") 14 | class AndroidTVPlugin : Plugin(), IBroadcastObserver { 15 | 16 | override fun handleOnDestroy() { 17 | super.handleOnDestroy() 18 | Globals.UnregisterObserver(this) 19 | registered = false; 20 | } 21 | override fun handleOnPause() { 22 | super.handleOnDestroy() 23 | Globals.UnregisterObserver(this) 24 | registered = false; 25 | } 26 | 27 | override fun handleOnResume() { 28 | super.handleOnResume() 29 | if(!registered){ 30 | registered =true 31 | Globals.RegisterObserver(this) 32 | } 33 | } 34 | 35 | override fun load(){ 36 | if(!registered){ 37 | registered =true 38 | Globals.RegisterObserver(this) 39 | } 40 | } 41 | 42 | @PluginMethod 43 | fun getIp(call: PluginCall) { 44 | val ret = JSObject() 45 | ret.put("value", localIp ?: "") 46 | call.resolve(ret) 47 | } 48 | 49 | @PluginMethod 50 | fun get(call: PluginCall) { 51 | val uiModeManager: UiModeManager = 52 | App.context.getSystemService(UI_MODE_SERVICE) as UiModeManager 53 | val ret = JSObject() 54 | if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { 55 | ret.put("value", true) 56 | } else { 57 | ret.put("value", false) 58 | } 59 | call.resolve(ret) 60 | } 61 | 62 | override fun update(action: String?, value: String?) { 63 | if (action.equals("WSLOGIN")) { 64 | try { 65 | notifyListeners("login", JSObject(value)) 66 | } catch (e: Exception) { 67 | 68 | } 69 | } 70 | } 71 | 72 | companion object{ 73 | var registered: Boolean = false 74 | } 75 | } -------------------------------------------------------------------------------- /android/app/src/main/res/layout/fragment_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 25 | 26 | 34 | 35 | 43 | 44 | 54 | 55 | 77 | )} 78 | {websocketConnected && ( 79 | 85 | )} 86 | 87 | logo 88 | 89 |
90 | 98 |
99 | 100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/tech/logica10/soniclair/App.kt: -------------------------------------------------------------------------------- 1 | package tech.logica10.soniclair 2 | 3 | import android.app.Application 4 | import android.app.UiModeManager 5 | import android.content.Context 6 | import android.content.res.Configuration 7 | import android.util.Log 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.launch 11 | import java.net.InetAddress 12 | import java.net.InterfaceAddress 13 | import java.net.NetworkInterface 14 | import java.util.* 15 | 16 | class App : Application() { 17 | private var udpServer: UDPServer? = null 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | application = this 22 | CoroutineScope(Dispatchers.IO).launch { 23 | // If we're logged in a server 24 | if (KeyValueStorage.getActiveAccount().username != null) { 25 | val songs = KeyValueStorage.getCachedSongs() 26 | // If we don't already have cached items 27 | if (songs.isEmpty()) { 28 | // Repopulate the media items cache 29 | val subsonicClient = SubsonicClient(KeyValueStorage.getActiveAccount()) 30 | val s = subsonicClient.getRandomSongs() 31 | KeyValueStorage.setCachedSongs(s) 32 | } 33 | } 34 | return@launch 35 | } 36 | val uiModeManager: UiModeManager = 37 | this.applicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager 38 | 39 | if (uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION) { 40 | isTv = true 41 | 42 | } 43 | try { 44 | val interfaces: List = 45 | Collections.list(NetworkInterface.getNetworkInterfaces()) 46 | for (intf in interfaces) { 47 | if (intf.name.subSequence( 48 | 0, 49 | 2 50 | ) == "rm" || (intf.name.length >= 6 && intf.name.subSequence( 51 | 0, 52 | 5 53 | ) == "radio") 54 | ) { 55 | continue 56 | } 57 | val addrs: MutableList = intf.interfaceAddresses 58 | for (addr in addrs) { 59 | val a: String = addr.address.hostAddress!!.replace("/", "") 60 | if (!addr.address.isLoopbackAddress 61 | && (a.subSequence(0, 7) == "192.168" 62 | || a.subSequence(0, 2) == "10" 63 | || a.subSequence(0, 3) == "172") 64 | ) { 65 | localIp = a 66 | localBroadcast = addr.broadcast.hostAddress!! 67 | } 68 | } 69 | } 70 | 71 | } catch (ex: Exception) { 72 | Log.e("SonicLair", ex.message!!) 73 | } // for now eat exceptions 74 | 75 | udpServer = UDPServer( 76 | InetAddress.getByName(localIp), 77 | InetAddress.getByName(localBroadcast), 78 | isTv 79 | ) 80 | if (isTv) { 81 | server = MessageServer(30001) 82 | server!!.start() 83 | CoroutineScope(Dispatchers.IO).launch { 84 | 85 | udpServer!!.receiveUDP() 86 | } 87 | } 88 | } 89 | 90 | companion object { 91 | var application: Application? = null 92 | private set 93 | 94 | @JvmStatic 95 | val context: Context 96 | get() = application!!.applicationContext 97 | var localIp: String? = null 98 | var localBroadcast: String? = null 99 | var server: MessageServer? = null 100 | var isTv: Boolean = false 101 | } 102 | } -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 12 | 16 | 20 | 27 | 34 | 38 | 39 | 40 | --------------------------------------------------------------------------------